Linux笔记
第 1 章 Linux管理
1.1 Linux基本指令
1.1.1 addr2line
addr2line –e pgw 0Xfffff
另外:dmesg |grep pgw
注意:编译pgw需添加-g选项才可以找到所在行
1.1.2 alias
重命名: alias killim=”kill $(ps –A | awk ‘/im_test/{print $1}’)”
1.1.3 ar
ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的member)。原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。
下面是ar命令的格式:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
例如我们可以用ar rv libtest.a hello.o hello1.o来生成一个库,库名字是test,链接时可以用-ltest链接。该库中存放了两个模块hello.o和hello1.o。选项前可以有‘-‘字符,也可以没有。下面我们来看看命令的操作选项和任选项。现在我们把{dmpqrtx}部分称为操作选项,而[abcfilNoPsSuvV]部分称为任选项。
{dmpqrtx}中的操作选项在命令中只能并且必须使用其中一个,它们的含义如下:
l d:从库中删除模块。按模块原来的文件名指定要删除的模块。如果使用了任选项v则列出被删除的每个模块。
l m:该操作是在一个库中移动成员。当库中如果有若干模块有相同的符号定义(如函数定义),则成员的位置顺序很重要。如果没有指定任选项,任何指定的成员将移到库的最后。也可以使用’a’,’b’,或’I’任选项移动到指定的位置。
l p:显示库中指定的成员到标准输出。如果指定任选项v,则在输出成员的内容前,将显示成员的名字。如果没有指定成员的名字,所有库中的文件将显示出来。
l q:快速追加。增加新模块到库的结尾处。并不检查是否需要替换。’a’,’b’,或’I’任选项对此操作没有影响,模块总是追加的库的结尾处。如果使用了任选项v则列出每个模块。 这时,库的符号表没有更新,可以用’ar s’或ranlib来更新库的符号表索引。
l r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。
l t:显示库的模块表清单。一般只显示模块名。
l x:从库中提取一个成员。如果不指定要提取的模块,则提取库中所有的模块。
下面在看看可与操作选项结合使用的任选项:
l a:在库的一个已经存在的成员后面增加一个新的文件。如果使用任选项a,则应该为命令行中membername参数指定一个已经存在的成员名。
l b:在库的一个已经存在的成员前面增加一个新的文件。如果使用任选项b,则应该为命令行中membername参数指定一个已经存在的成员名。
l c:创建一个库。不管库是否存在,都将创建。
l f:在库中截短指定的名字。缺省情况下,文件名的长度是不受限制的,可以使用此参数将文件名截短,以保证与其它系统的兼容。
l i:在库的一个已经存在的成员前面增加一个新的文件。如果使用任选项i,则应该为命令行中membername参数指定一个已经存在的成员名(类似任选项b)。
l l:暂未使用
l N:与count参数一起使用,在库中有多个相同的文件名时指定提取或输出的个数。
l o:当提取成员时,保留成员的原始数据。如果不指定该任选项,则提取出的模块的时间将标为提取出的时间。
l P:进行文件名匹配时使用全路径名。ar在创建库时不能使用全路径名(这样的库文件不符合POSIX标准),但是有些工具可以。
l s:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引。甚至对于没有任何变化的库也作该动作。对一个库做ar s等同于对该库做ranlib。
l S:不创建目标文件索引,这在创建较大的库时能加快时间。
l u:一般说来,命令ar r…插入所有列出的文件到库中,如果你只想插入列出文件中那些比库中同名文件新的文件,就可以使用该任选项。该任选项只用于r操作选项。
l v:该选项用来显示执行操作选项的附加信息。
l V:显示ar的版本。
1.1.4 Arp
Arp –a
显示所有ARP
1.1.5 arping
Linux arping命令测试IP地址冲突
arping命令可以用来测试局域网各个主机之间的连通性,测试局域网中某个特定的IP地址是否已经被占用,进而可以有效检测局域网内的IP地址冲突问题。
如下图示例:arping -c 3 -f -D ...(..*.*为IP地址)
192.168.2.222返回空,说明这个IP地址没有被局域网占用。
192.168.2.106返回1,说明这个IP地址已经被占用,并且收到回复可以看到绑定该IP的终端的mac地址。
arping命令详细介绍:
用法:arping [-fqbDUAV] [-c count] [-w timeout] [-I device] [-s source] ip地址
参数:
-A ARP回复模式,更新邻居
-b 保持广播
-D 复制地址检测模式
-f 得到第一个回复就 退出
-q 不显示警告信息
-U 主动的ARP模式,更新邻居
-c<数据包的数目> 发送的数据包的数目
-w<超时时间> 设置超时时间
-I<网卡> 使用指定的以太网设备,默认情况下使用eth0
-s 指定源IP地址
-h 显示帮助信息
-V 显示版本信息
1.1.6 awk
ls –l |awk ‘{print $1}’ 输出打印第一列结果
将得到的结果赋值给变量,如:
a=$(ps –a|awk ‘/tcpdump/{print $1}’)
ó a=$(ps –a|grep tcpdump |awk ‘{print $1}’)
1.1.7 basename
打印目录或文件的基本名称
1.1.8 cat
显示指定的一个或多个文件内容
如,cat –n hello.c a.c …
-n 由1开始对所有输出的行进行编号
-b 空白行不编号
tac** ** 从最后一行开始显示文件内容
more 分页显示文本文件内容
less
head 显示文件首部内容
tail
e.g. head -2 a.txt 显示当前目录下的a.txt的前两行
tail -3 a.txt 显示当前目录下的a.txt的最后3行
1.1.9 cd
cd - 回到前次工作目录
cd .. 回到上级目录
cd ~ 当前用户的家目录
e.g. cd ~jaky 切换到jaky的家目录
1.1.10 chown/chgrp/chmod
chown 修改文件所有组和组别
chgrp 修改文件组所有权
chmod 修改文件访问权限
e.g. chown -R root a.tar 修改a.tar文件的所有者为root用户
chmod u=rwx,g=rx,o=x house/ 修改house目录权限
-R 表示包括其子目录也同时修改
1.1.11 chroot
改变根目录,即将某个目录作为/
1.1.12 cp
复制文件或目录
-r 递归复制,复制目录和目录下的所有文件
-v 输出文字说明
格式:cp 原文件或目录 目标文件或目录
e.g. cp /root/*.docx /root/doc 将/root目录下所有以”.docx”结尾的文件复制到/root/doc目录下
复制时覆盖原有文件:
修改/root/.bashrc下的alias
\cp A B
1.1.13 cut
-d 指定分隔符
-f 指定域
cat /etc/redhat-release|cut –d ”.” –f 1 取出“.”右边的内容
示例:
1.1.14 df和du
df可以查询系统中各个存储设备的使用情况
-h:使用单位显示存储空间大小
du可以查询文件所占用的磁盘空间大小
-c 最后再输出所有文件占用空间的总和
查看磁盘剩余空间 df -k
查看当前目录大小 du -sh
查看当前目录所在分区大小 du -h
存储情况
1.1.15 dos2unix
sudo find /home -name “*.c”|xargs dos2unix
1.1.16 echo
显示打印
echo hello 显示hello
不换行: echo –n hello
显示多行 echo –e “xxx \n”
1.1.17 env
查看当前环境变量
常用环境变量PATH
添加环境变量 /home/share $PATH=$PATH:/home/share
使环境变量生效 source /etc/profile
export 设置一个环境变量,当前shell的所有子进程都可以访问这个环境变量
1.1.17.1 各环境变量意义
PATH: 可执行文件的目录
LIBRARY_PATH:静态库文件目录
LD_LIBRARY_PATH:动态库文件目录
gcc默认的include目录C_INCLUDE_PATH
g++默认的include目录CPLUS_INCLUDE_PATH
1.1.18 ethtool
获取、修改以太网卡配置信息
1.1.19 file
显示指定文件的类型
1.1.20 fdisk -l
查看u盘分区
1.1.21 find
e.g. find ./ -name hello*.c
find /boot -size -10K 查找boot目录下小于10k的文件
查找某目录下含内容test
find ./ |xargs grep “test”
1.1.22 free
查询系统当前的内存使用情况
-b 以字节为单位显示
-k KB
-m MB
-g GB
-s delay 每隔delay秒刷新一次
1.1.23 fuser
fuser find files or sockets’ user
1.1.24 getconf
getconf LONG_BIT 得到主机的位数
1.1.25 gzip
gzip a 压缩a文件为gzip格式
gzip -d a.gz 解压
1.1.26 grep
指定文件中搜索特定的内容,并将这些内容的行标准输出
-I 忽略大小写
-r 递归搜索,如果是目录,则搜索目录下的所有文件。
-c 统计
e.g. grep “hello” / -r
grep pattern1|pattern2 files 显示匹配pattern1或pattern2的行
grep pattern1 files | grep pattern2 files ..与..
grep –c “/usr/sbin/logrotate /etc/logrotateim.conf” /etc/crontab
grep(global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
1.1.26.1 选项
-a 不要忽略二进制数据。
-A<显示列数> 除了显示符合范本样式的那一行之外,并显示该行之后的内容。
-b 在显示符合范本样式的那一行之外,并显示该行之前的内容。
-c 计算符合范本样式的列数。
-C<显示列数>或-<显示列数> 除了显示符合范本样式的那一列之外,并显示该列之前后的内容。
-d<进行动作> 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。
-e<范本样式> 指定字符串作为查找文件内容的范本样式。
-E 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式。
-f<范本文件> 指定范本文件,其内容有一个或多个范本样式,让grep查找符合范本条件的文件内容,格式为每一列的范本样式。
-F 将范本样式视为固定字符串的列表。
-G 将范本样式视为普通的表示法来使用。
-h 在显示符合范本样式的那一列之前,不标示该列所属的文件名称。
-H 在显示符合范本样式的那一列之前,标示该列的文件名称。
-i 胡列字符大小写的差别。
-l 列出文件内容符合指定的范本样式的文件名称。
-L 列出文件内容不符合指定的范本样式的文件名称。
-n 在显示符合范本样式的那一列之前,标示出该列的编号。
-q 不显示任何信息。
-R/-r 此参数的效果和指定“-d recurse”参数相同。
-s 不显示错误信息。
-v 反转查找。
-w 只显示全字符合的列。
-x 只显示全列符合的列。
-y 此参数效果跟“-i”相同。
-o 只输出文件中匹配到的部分。
1.1.26.2 grep命令常见用法
在文件中搜索一个单词,命令会返回一个包含“match_pattern”的文本行:
grep match_pattern file_name
grep “match_pattern” file_name
在多个文件中查找:
grep “match_pattern” file_1 file_2 file_3 …
输出除之外的所有行 -v 选项:
grep -v “match_pattern” file_name
标记匹配颜色 –color=auto 选项:
grep “match_pattern” file_name –color=auto
使用正则表达式 -E 选项:
grep -E “[1-9]+”
或
egrep “[1-9]+”
只输出文件中匹配到的部分 -o 选项:
echo this is a test line. | grep -o -E “[a-z]+.“
line.
echo this is a test line. | egrep -o “[a-z]+.“
line.
统计文件或者文本中包含匹配字符串的行数 -c 选项:
grep -c “text” file_name
输出包含匹配字符串的行数 -n 选项:
grep “text” -n file_name
或
cat file_name | grep “text” -n
#多个文件
grep “text” -n file_1 file_2
打印样式匹配所位于的字符或字节偏移:
echo gun is not unix | grep -b -o “not”
7:not
#一行中字符串的字符便宜是从该行的第一个字符开始计算,起始值为0。选项 -b -o 一般总是配合使用。
搜索多个文件并查找匹配文本在哪些文件中:
grep -l “text” file1 file2 file3…
grep递归搜索文件
在多级目录中对文本进行递归搜索:
grep “text” . -r -n
# .表示当前目录。
忽略匹配样式中的字符大小写:
echo “hello world” | grep -i “HELLO”
hello
选项 -e 制动多个匹配样式:
echo this is a text line | grep -e “is” -e “line” -o
is
line
#也可以使用-f选项来匹配多个样式,在样式文件中逐行写出需要匹配的字符。
cat patfile
aaa
bbb
echo aaa bbb ccc ddd eee | grep -f patfile -o
在grep搜索结果中包括或者排除指定文件:
#只在目录中所有的.php和.html文件中递归搜索字符”main()”
grep “main()” . -r –include *.{php,html}
#在搜索结果中排除所有README文件
grep “main()” . -r –exclude “README”
#在搜索结果中排除filelist文件列表里的文件
grep “main()” . -r –exclude-from filelist
使用0值字节后缀的grep与xargs:
#测试文件:
echo “aaa” > file1
echo “bbb” > file2
echo “aaa” > file3
grep “aaa” file* -lZ | xargs -0 rm
#执行后会删除file1和file3,grep输出用-Z选项来指定以0值字节作为终结符文件名(\0),xargs -0 读取输入并用0值字节终结符分隔文件名,然后删除匹配文件,-Z通常和-l结合使用。
grep静默输出:
grep -q “test” filename
#不会输出任何信息,如果命令运行成功返回0,失败则返回非0值。一般用于条件测试。
打印出匹配文本之前或者之后的行:
#显示匹配某个结果之后的3行,使用 -A 选项:
seq 10 | grep “5” -A 3
5
6
7
8
#显示匹配某个结果之前的3行,使用 -B 选项:
seq 10 | grep “5” -B 3
2
3
4
5
#显示匹配某个结果的前三行和后三行,使用 -C 选项:
seq 10 | grep “5” -C 3
2
3
4
5
6
7
8
#如果匹配结果有多个,会用“–”作为各匹配结果之间的分隔符:
echo -e “a\nb\nc\na\nb\nc” | grep a -A 1
a
b
--
a
b
1.1.27 head
head –n 3 test.log
显示test.log的前3行,不指定的情况显示10行,如:head test.log
1.1.28 help
帮助命令
1.1.29 id
id –u == 0 root用户
1.1.30 ifconfig
显示ip地址
1.1.31 ifstat
统计网络接口活动状态工具
1.1.32 iostat
监视系统输入输出设备和cpu的使用情况
1.1.33 iotop
监视磁盘IO使用情况的top类工具
1.1.34 ip
ip addr 显示地址
1.1.35 iptraf
实时查看网卡流量,可以生成网络协议数据包信息、以太网信息等
1.1.36 ipcs
分析消息队列、共享内存和信号量
1.1.37 iptables
iptables -F 清除预设表filter中的所有规则链的规则
1.1.38 init
init 0、1、6
0 切换到运行级别0,代表关机
1 代表单用户模式
6 代表重新启动
init 0 关机
init 3 纯命令行模式
init 5 图形界面形式
init 6 重启
1.1.39 kill
向进程发送信号
1.1.40 ldd
打印程序或库文件所依赖的共享库列表
1.1.41 ln
链接文件或目录
ln -s /abc /root/f abc创建快捷方式名为f,存放于root文件夹下
1.1.42 locate
locate grab 查找名为grab的文件或目录
查找文件
find / -name filename,这个很费时间,直接找的整个硬盘
locate filename
whereis *filename *这个好像能找到以前删除的,有几次看到了删除的都能找到
1.1.43 logger
logger [options] [messages]
1.1.43.1 options (选项)
Ø -d, –udp
使用数据报(UDP)而不是使用默认的流连接(TCP)
Ø -i, –id
逐行记录每一次logger的进程ID
Ø -f, –file file_name
记录特定的文件
Ø -h, –help
显示帮助文本并退出
Ø -n, –server
写入指定的远程syslog服务器,使用UDP代替内装式syslog的例程
Ø -P, –port port_num
使用指定的UDP端口。默认的端口号是514
Ø -p, –priority priority_level
指定输入消息的优先级,优先级可以是数字或者指定为 “ facility.level” 的格式。比如:” -p local3.info “ local3 这个设备的消息级别为 info。默认级别是 “user.notice”
Ø -s, –stderr
输出标准错误到系统日志。
Ø -t, –tag tag
指定标记记录
Ø -u, –socket socket
写入指定的socket,而不是到内置系统日志例程。
Ø -V, –version
现实版本信息并退出
1.1.43.2 messages
写入log文件的内容消息,可以与-f配合使用。
logger 以0退出表示成功,大于0表示失败。
1.1.43.3 日志级别
facility:
auth: 用户授权
authpriv: 授权和安全
cron: 计划任务
daemon: 系统守护进程
kern: 与内核有关的信息
lpr 与打印服务有关的信息
mail 与电子邮件有关的信息
news 来自新闻服务器的信息
syslog 由syslog生成的信息
user 用户的程序生成的信息,默认
uucp 由uucp生成的信息
local0~7 用来定义本地策略
level:
alert 需要立即采取动作
crit 临界状态
debug 调试
emerg 系统不可用
err 错误状态
error 错误状态
info 正常消息
notice 正常但是要注意
1.1.44 logsave
将命令的输出保存到指定的日志文件中
logsave –a log.txt tree
1.1.45 ls
ls –l (ll) 显示文件属性,文件权限
ls -a 显示包括隐藏文件的所有文件及目录
ls -lh 以人类可读方式显示文件大小
1.1.46 lsof|grep deleted
当删掉某个文件后,硬盘空间并没有立即释放,采用上述命令,找出那个文件在使用,重启调用该文件的程序即可。
1.1.47 ltrace
跟踪进程调用库函数的情况
1.1.48 make
-C dir 执行时进入dir目录,默认当前目录
-f file 使用file 做为Makefile
1.1.49 man
获得帮助
man [cmd]
man 2 [system call]
man 3 [lib function call]
e.g. man 2 creat
man 3 printf
1.1.50 mkdir
创建目录
-p 如果file的父目录不存在,则先创建父目录
-v 对于创建的每一个目录输出一条说明
e.g. mkdir /abc 在根目录下创建abc目录
mkdir -p /doc/abcd 在创建doc目录的同时创建abcd目录
touch 新建文件
1.1.51 mount/umount
挂载或卸载设备
mount 查看挂载情况
mount /dev/sdb6 /mnt/sdb6 挂载磁盘分区
mout /dev/cdrom /mnt/cdrom 挂载光驱
mount /dev/sdc1 /mnt/sdc1 挂载u盘
mount –t vfat /dev/sdb /mnt/udisk 挂载u盘
umout /mnt/udisk 卸载u盘
1.1.52 mpstat
多cpu下显示各个可用cpu状态
mpstat –P 1
1.1.53 mv
移动或更名现有文件或目录
e.g. mv a b 把a改名为b
1.1.54 nautilus
nautilus /home 打开图像化的home文件
1.1.55 netstat
netstat –tln 查看端口使用情况
netstat –a 查看所有端口
netstat –apn | grep ems 查看ems连接(打开)的所有端口
1.1.56 nm
nm用来列出目标文件的符号清单。下面是nm命令的格式:
nm [-a|–debug-syms] [-g|–extern-only] [-B][-C|–demangle] [-D|–dynamic] [-s|–print-armap][-o|–print-file-name] [-n|–numeric-sort][-p|–no-sort] [-r|–reverse-sort] [–size-sort][-u|–undefined-only] [-l|–line-numbers] [–help][–version] [-t radix|–radix=radix][-P|–portability] [-f format|–format=format][–target=bfdname] [objfile…]
如果没有为nm命令指出目标文件,则nm假定目标文件是a.out。下面列出该命令的任选项,大部分支持”-“开头的短格式和”—“开头的长格式。
-A、-o或–print-file-name:在找到的各个符号的名字前加上文件名,而不是在此文件的所有符号前只出现文件名一次。
例如nm libtest.a的输出如下:
CPThread.o:
00000068 T Main__8CPThreadPv
00000038 T Start__8CPThread
00000014 T _._8CPThread
00000000 T __8CPThread
00000000 ? FRAME_BEGIN
…………………………………
则nm -A 的输出如下:
libtest.a:CPThread.o:00000068 T Main__8CPThreadPv
libtest.a:CPThread.o:00000038 T Start__8CPThread
libtest.a:CPThread.o:00000014 T _._8CPThread
libtest.a:CPThread.o:00000000 T __8CPThread
libtest.a:CPThread.o:00000000 ? FRAME_BEGIN
…………………………………………………………
l -a或–debug-syms:显示调试符号。
l -B:等同于–format=bsd,用来兼容MIPS的nm。
l -C或–demangle:将低级符号名解码(demangle)成用户级名字。这样可以使得C++函数名具有可读性。
l -D或–dynamic:显示动态符号。该任选项仅对于动态目标(例如特定类型的共享库)有意义。
l -f format:使用format格式输出。format可以选取bsd、sysv或posix,该选项在GNU的nm中有用。默认为bsd。
l -g或–extern-only:仅显示外部符号。
l -n、-v或–numeric-sort:按符号对应地址的顺序排序,而非按符号名的字符顺序。
l -p或–no-sort:按目标文件中遇到的符号顺序显示,不排序。
l -P或–portability:使用POSIX.2标准输出格式代替默认的输出格式。等同于使用任选项-f posix。
l -s或–print-armap:当列出库中成员的符号时,包含索引。索引的内容包含:哪些模块包含哪些名字的映射。
l -r或–reverse-sort:反转排序的顺序(例如,升序变为降序)。
l –size-sort:按大小排列符号顺序。该大小是按照一个符号的值与它下一个符号的值进行计算的。
l -t radix或–radix=radix:使用radix进制显示符号值。radix只能为”d”表示十进制、”o”表示八进制或”x”表示十六进制。
l –target=bfdname:指定一个目标代码的格式,而非使用系统的默认格式。
l -u或–undefined-only:仅显示没有定义的符号(那些外部符号)。
l -l或–line-numbers:对每个符号,使用调试信息来试图找到文件名和行号。对于已定义的符号,查找符号地址的行号。对于未定义符号,查找指向符号重定位入口的行号。如果可以找到行号信息,显示在符号信息之后。
l -V或–version:显示nm的版本号。
l –help:显示nm的任选项。
1.1.57 nohup
不挂断运行命令
nohup wireshark >/dev/null &
1.1.58 nslookup
Nslookup(name server lookup)( 域名查询):是一个用于查询 Internet域名信息或诊断DNS 服务器问题的工具.
nslookup
> www.hnu.edu.cn 要查询的主机域名
…
Address 解析的IP地址
> exit 退出
1.1.59 passwd
用于修改用户密码
-S 查看指定用户的密码状态
-a 与-S结合查看所有用户的密码状态
-d 删除指定用户的密码
1.1.60 ping
测试网络连通性
-a 收到响应发出声音
-c count 发送count 个,默认无限
-i interval 发送间隔,默认1s
ping -c 3 192.168.1.1
-I eth/ip 通过制定ip或端口
注:windows
1.1.61 pmap
报告进程的内存映射关系
pmap [pid]
-x 显示扩展格式
1.1.62 prstat
显示系统当前运行的进程和项目的各种信息
1.1.63 ps
查看系统中的进程
-e显示所有进程
-l 输出关于进程更详细信息
1.1.64 pstree
以树形结构显示进程
1.1.65 pstack
查看进程的栈跟踪
pstack pidof mme
1.1.66 pwd
查看当前目录
1.1.67 ranlib
更新静态库符号索引表
1.1.68 read
1.1.68.1 基本读取
read命令接收标准输入(键盘)的输入,或其他文件描述符的输入(后面在说)。得到输入后,read命令将数据放入一个标准变量中。下面是 read命令的最简单形式。
#!/bin/bash
echo -n “Enter your name:” //参数-n的作用是不换行,echo默认是换行
read name //从键盘输入
echo “hello $name,welcome to my program” //显示信息
exit 0 //退出shell程序。
//********************************
由于read命令提供了-p参数,允许在read命令行中直接指定一个提示。
所以上面的脚本可以简写成下面的脚本::
#!/bin/bash
read -p “Enter your name:” name
echo “hello $name, welcome to my program”
exit 0
在上面read后面的变量只有name一个,也可以有多个,这时如果输入多个数据,则第一个数据给第一个变量,第二个数据给第二个变量,如果输入数 据个数过多,则最后所有的值都给第一个变量。如果太少输入不会结束。
//*****************************************
在read命令行中也可以不指定变量.如果不指定变量,那么read命令会将接收到的数据放置在环境变量REPLY中。
例如::
read -p “Enter a number”
环境变量REPLY中包含输入的所有数据,可以像使用其他变量一样在shell脚本中使用环境变量REPLY.
1.1.68.2 计时输入.
使用read命令存在着潜在危险。脚本很可能会停下来一直等待用户的输入。如果无论是否输入数据脚本都必须继续执行,那么可以使用-t选项指定一个 计时器。
-t选项指定read命令等待输入的秒数。当计时满时,read命令返回一个非零退出状态;
#!/bin/bash
if read -t 5 -p “please enter your name:” name
then
echo “hello $name ,welcome to my script”
else
echo “sorry,too slow”
fi
exit 0
除了输入时间计时,还可以设置read命令计数输入的字符。当输入的字符数目达到预定数目时,自动退出,并将输入的数据赋值给变量。
#!/bin/bash
read -n1 -p “Do you want to continue [Y/N]?” answer
case $answer in
Y | y)
echo “fine ,continue”;;
N | n)
echo “ok,good bye”;;
*)
echo “error choice”;;
esac
exit 0
该例子使用了-n选项,后接数值1,指示read命令只要接受到一个字符就退出。只要按下一个字符进行回答,read命令立即接受输入并将其传给变量。无需按回车键。
1.1.68.3 默读(输入不显示在监视器上)
有时会需要脚本用户输入,但不希望输入的数据显示在监视器上。典型的例子就是输入密码,当然还有很多其他需要隐藏的数据。
-s选项能够使read命令中输入的数据不显示在监视器上(实际上,数据是显示的,只是 read命令将文本颜色设置成与背景相同的颜色)。
#!/bin/bash
read -s -p “Enter your password:” pass
echo “your password is $pass”
exit 0
1.1.68.4 读文件
最后,还可以使用read命令读取Linux系统上的文件。
每次调用read命令都会读取文件中的”一行”文本。当文件没有可读的行时,read命令将以非零状态退出。
读取文件的关键是如何将文本中的数据传送给read命令。
最常用的方法是对文件使用cat命令并通过管道将结果直接传送给包含read命令的 while命令
例子::
#!/bin/bash
count=1 //赋值语句,不加空格
cat test | while read line //cat 命令的输出作为read命令的输入,read读到的值放在line中
do
echo “Line $count:$line”
count=$[ $count + 1 ] //注意中括号中的空格。
done
echo “finish”
exit 0
1.1.69 readelf
读取elf文件信息
-a 所有
1.1.70 rm
remove删除文件
-r 递归删除,删除目录和目录下的所有文件
rm -rf 目录名 会把目录名的文件夹删掉,建议使用
-f 强制删除
-v 输出说明文字
rm -f !(a) 最为方便。如果保留a和b,可以运行rm -f !(a|b)来实现。
1.1.71 rmdir
删除目录
rm -rf
1.1.72 route
显示路由信息
1.1.72.1 添加路由
route add -net 192.168.0.0/24 gw 192.168.0.1
route add -host 192.168.1.1 dev 192.168.0.1
1.1.72.2 删除路由
route del -net 192.168.0.0/24 gw 192.168.0.1
1.1.72.3 增加默认路由
route add default gw 192.168.0.1
默认路由一条就够了
route -n 查看路由表
add 增加路由
del 删除路由
-net 设置到某个网段的路由
-host 设置到某台主机的路由
gw 出口网关 IP地址
dev 出口网关 物理设备名
1.1.72.4 添加路由2
ip route add 192.168.0.0/24 via 192.168.0.1
ip route add 192.168.1.1 dev 192.168.0.1
删除路由
ip route del 192.168.0.0/24 via 192.168.0.1
add 增加路由
del 删除路由
via 网关出口 IP地址
dev 网关出口 物理设备名
1.1.72.5 增加默认路由2
ip route add default via 192.168.0.1 dev eth0
via 192.168.0.1 是我的默认路由器
1.1.72.6 查看路由信息
ip route
保存路由设置,使其在网络重启后任然有效
在/etc/sysconfig/network-script/目录下创建名为route- eth0的文件
vi /etc/sysconfig/network-script/route-eth0
在此文件添加如下格式的内容
192.168.1.0/24 via 192.168.0.1
重启网络验证
/etc/rc.d/init.d/network中有这么几行:
# Add non interface-specific static-routes.
if [ -f /etc/sysconfig/static-routes ]; then
grep “^any” /etc/sysconfig/static-routes | while read ignore args ; do
/sbin/route add -$args
done
fi
也就是说,将静态路由加到/etc/sysconfig/static-routes 文件中就行了。
如加入:
route add -net 11.1.1.0 netmask 255.255.255.0 gw 11.1.1.1
则static-routes的格式为
any net 11.1.1.0 netmask 255.255.255.0 gw 11.1.1.1
1.1.72.7 高级功能-策略路由
基于策略的路由比传统路由在功能上更强大,使用更灵活,它使网络管理员不仅能够根据目的地址而且能够根据报文大小、应用或IP源地址来选择转发路径…
#/etc/iproute2/rt_tables 此文件存有linux 系统路由表默认表有255 254 253三张表
255 local 本地路由表 存有本地接口地址,广播地址,已及NAT地址.
local表由系统自动维护..管理员不能操作此表…
254 main 主路由表 传统路由表,ip route若没指定表亦操作表254.一般存所有的路由..
注:平时用ip ro sh查看的亦是此表设置的路由.
253 default 默认路由表一般存放默认路由…
注:rt_tables文件中表以数字来区分表0保留最多支持255张表
路由表的查看可有以下二种方法:
#ip route list table table_number
#ip route list table table_name
1.1.73 rpm
安装管理包
rpm -ivh /a.e15.i386.rpm
-i 安装
-v 查看详细安装信息
-h 安装进度
rpm -qa q查询 a所有包
rpm -ivh p.rpm i 安装p.rpm到系统上
v 执行时显示命令执行过程
h 显示安装过程
rpm -ivh package.rpm 安装一个rpm包
rpm -qa 显示系统中所有已经安装的rpm包
rpm –qa httpd
rpm -qa | grep httpd 显示所有名称中包含 “httpd” 字样的rpm包
1.1.73.1 强制卸载
Rpm –nodeps -e test.rpm
1.1.73.2 详解
二进制包(Binary)以及源代码包(Source)两种。二进制包可以直接安装在计算机中,而源代码包将会由 RPM自动编译、安装。源代码包经常以src.rpm作为后缀名。
常用命令组合:
-ivh:安装显示安装进度–install–verbose–hash
-Uvh:升级软件包–Update;
-qpl: 列出RPM软件包内的文件信息[Query Package list];
-qpi:列出RPM软件包的描述信息[Query Package install package(s)];
-qf:查找指定文件属于哪个RPM软件包[Query File];
-Va:校验所有的 RPM软件包,查找丢失的文件[View Lost];
-e:删除包
rpm -q samba //查询程序是否安装
rpm -ivh /media/cdrom/RedHat/RPMS/samba-3.0.10-1.4E.i386.rpm //按路径安装并显示进度
rpm -ivh –relocate /=/opt/gaim gaim-1.3.0-1.fc4.i386.rpm //指定安装目录
rpm -ivh –test gaim-1.3.0-1.fc4.i386.rpm //用来检查依赖关系;并不是真正的安装;
rpm -Uvh –oldpackage gaim-1.3.0-1.fc4.i386.rpm //新版本降级为旧版本
rpm -qa | grep httpd #[搜索指定rpm包是否安装]–all搜索httpd
rpm -ql httpd #[搜索rpm包]–list所有文件安装目录
rpm -qpi Linux-1.4-6.i368.rpm #[查看rpm包]–query–package–install package信息
rpm -qpf Linux-1.4-6.i368.rpm #[查看rpm包]–file
rpm -qpR file.rpm #[查看包]依赖关系
rpm2cpio file.rpm |cpio -div #[抽出文件]
rpm -ivh file.rpm #[安装新的rpm]–install–verbose–hash
rpm -ivh [url]http://mirrors.kernel.org/fedora/core/4/i386/os/Fedora/RPMS/gaim-1.3.0-1.fc4.i386.rpm[/url]
rpm -Uvh file.rpm #[升级一个rpm]–upgrade
rpm -e file.rpm #[删除一个rpm包]–erase
常用参数:
Install/Upgrade/Erase options:
-i, –install install package(s)
-v, –verbose provide more detailed output
-h, –hash print hash marks as package installs (good with -v)
-e, –erase erase (uninstall) package
-U, –upgrade=
--replacepkge 无论软件包是否已被安装,都强行安装软件包
–test 安装测试,并不实际安装
–nodeps 忽略软件包的依赖关系强行安装
–force 忽略软件包及文件的冲突
Query options (with -q or –query):
-a, –all query/verify all packages
-p, –package query/verify a package file
-l, –list list files in package
-d, –docfiles list all documentation files
-f, –file query/verify package(s) owning file
RPM源代码包装安装
.src.rpm结尾的文件,这些文件是由软件的源代码包装而成的,用户要安装这类RPM软件包,必须使用命令:
rpm –recompile vim-4.6-4.src.rpm #这个命令会把源代码解包并编译、安装它,如果用户使用命令:
rpm –rebuild vim-4.6-4.src.rpm #在安装完成后,还会把编译生成的可执行文件重新包装成i386.rpm 的RPM软件包。
[root@localhost src]# rpm -ivh awstats-6.8-1.noarch.rpm
error: Failed dependencies:
perl(LWP::UserAgent) is needed by awstats-6.8-1.noarch
[root@localhost src]#
使用rpm 属性相依套件的档案
[root@localhost src]# rpm -qpR awstats-6.8-1.noarch.rpm
/bin/sh
/usr/bin/perl
config(awstats) = 6.8-1
perl >= 0:5.005
perl(LWP::UserAgent)
perl(POSIX)
perl(Socket)
perl(Time::Local)
perl(strict)
perl(vars)
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
另外:# rpm -ivh –aid samba*.rpm (一定要安装rpmdb后才可以用这种方式安装排除依赖关系)
1.1.73.3 RPM安装
*.libs.rpm à 其他
1.1.74 rpmbuild
rpmbuild –ba ‘spec文件路径’
1.1.75 rpmdev-tools
1.1.75.1 rpmdev-newspec
1.1.75.2 rpmdev-newinit
1.1.76 sar
System Activity Reporter系统活动情况报告
显示系统状态计时器
sar –u 2 5 报告cpu使用情况,2s每次,统计5次
sar –r 2 5 报告内存使用情况,2s每次,统计5次
1.1.77 scp
复制本地文件到远程:
scp localfile root@1.1.1.1:/remotefile
得到远程文件:
scp root@192.168.210.1:/remotefile localfile
1.1.78 sed
cat /tmp/im_sys_log_temp | sed ‘s|($SystemLogRateLimitInterval)(.*)|\1 0 1’ >/tmp/im_sys_log.conf
( … ) 保留匹配的字符
* 匹配零或多个字符
. 匹配一个非换行符字符
1.1.78.1 命令详解
sed命令行格式为:
sed [-nefri] ‘command’ 输入文本/文件
常用选项:
-n∶取消默认的输出,使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN的资料一般都会被列出到屏幕上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来
-e∶进行多项编辑,即对输入行应用多条sed命令时使用. 直接在指令列模式上进行 sed 的动作编辑
-f∶指定sed脚本的文件名. 直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的sed 动作
-r∶sed 的动作支援的是延伸型正则表达式的语法。(预设是基础正则表达式语法)
-i∶直接修改读取的文件内容,而不是由屏幕输出
常用命令:
a ∶ 新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)
c ∶ 取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行
d ∶ 删除,因为是删除,所以 d 后面通常不接任何内容
i ∶ 插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行)
p∶ 列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n 一起用
s∶ 取代,可以直接进行替换的工作。通常这个 s 的动作可以搭配正则表达式。例如 1,20s/old/new/g
定址
定址用于决定对哪些行进行编辑。地址的形式可以是数字、正则表达式、或二者的结合。如果没有指定地址,sed将处理输入文件的所有行。
地址是一个数字,则表示行号;是“$”符号,则表示最后一行。例如:
只显示指定行范围的文件内容,例如:
# 只查看文件的第100行到第200行
sed -n ‘100,200p’ mysql_slow_query.log
地址是逗号分隔的,那么需要处理的地址是这两行之间的范围(包括这两行在内)。范围可以用数字、正则表达式、或二者的组合表示。例如:
1.1.78.2 举例:(假设我们有一文件名为ab)
1.1.78.2.1 删除某行
[root@localhost ruby] # sed ‘1d’ ab #删除第一行
[root@localhost ruby] # sed ‘$d’ ab #删除最后一行
[root@localhost ruby] # sed ‘1,2d’ ab #删除第一行到第二行
[root@localhost ruby] # sed ‘2,$d’ ab #删除第二行到最后一行
1.1.78.2.2 显示某行
[root@localhost ruby] # sed -n ‘1p’ ab #显示第一行
[root@localhost ruby] # sed -n ‘$p’ ab #显示最后一行
[root@localhost ruby] # sed -n ‘1,2p’ ab #显示第一行到第二行
[root@localhost ruby] # sed -n ‘2,$p’ ab #显示第二行到最后一行
1.1.78.2.3 使用模式进行查询
[root@localhost ruby] # sed -n ‘/ruby/p’ ab #查询包括关键字ruby所在所有行
[root@localhost ruby] # sed -n ‘/$/p’ ab #查询包括关键字$所在所有行,使 用反斜线\屏蔽特殊含义
1.1.78.2.4 增加一行或多行字符串
[root@localhost ruby]# cat ab
Hello!
ruby is me,welcome to my blog.
end
[root@localhost ruby] # sed ‘1a drink tea’ ab #第一行后增加字符串”drink tea”
Hello!
drink tea
ruby is me,welcome to my blog.
end
[root@localhost ruby] # sed ‘1,3a drink tea’ ab #第一行到第三行后增加字符串”drink tea”** Hello!
drink tea
ruby is me,welcome to my blog.
drink tea
end
drink tea
[root@localhost ruby] # sed ‘1a drink tea\nor coffee’ ab #第一行后增加多行,使用换行符\n
Hello!
drink tea
or coffee
ruby is me,welcome to my blog.
end
1.1.78.2.5 代替一行或多行
[root@localhost ruby] # sed ‘1c Hi’ ab #第一行代替为Hi
Hi
ruby is me,welcome to my blog.
end
[root@localhost ruby] # sed ‘1,2c Hi’ ab #第一行到第二行代替为Hi
Hi
end
1.1.78.2.6 替换一行中的某部分
格式:sed ‘s/要替换的字符串/新的字符串/g’ (要替换的字符串可以用正则表达式)
[root@localhost ruby] # sed -n ‘/ruby/p’ ab | sed ‘s/ruby/bird/g’ #替换ruby为bird
[root@localhost ruby] # sed -n ‘/ruby/p’ ab | sed ‘s/ruby//g’ #删除ruby
插入
[root@localhost ruby] # sed -i ‘$a bye’ ab #在文件ab中最后一行直接输入”bye”
[root@localhost ruby]# cat ab
Hello!
ruby is me,welcome to my blog.
end
bye
1.1.78.3 替换
-e是编辑命令,用于sed执行多个编辑任务的情况下。在下一行开始编辑前,所有的编辑动作将应用到模式缓冲区中的行上。
sed -e ‘1,10d’ -e ‘s/My/Your/g’ datafile
#选项-e用于进行多重编辑。第一重编辑删除第1-3行。第二重编辑将出现的所有My替换为Your。因为是逐行进行这两项编辑(即这两个命令都在模式空间的当前行上执行),所以编辑命令的顺序会影响结果。
# 替换两个或多个空格为一个空格 sed ‘s/[ ][ ]*/ /g’ file_name
# 替换两个或多个空格为分隔符: sed ‘s/[ ][ ]*/:/g’ file_name
# 如果空格与tab共存时用下面的命令进行替换
# 替换成空格 sed ‘s/[[:space:]][[:space:]]*/ /g’ filename
# 替换成分隔符: sed ‘s/[[:space:]][[:space:]]*/:/g’ filename
1.1.78.4 sed命令的调用
在命令行键入命令;将sed命令插入脚本文件,然后调用sed;将sed命令插入脚本文件,并使sed脚本可执行
sed [option] sed命令 输入文件 在命令行使用sed命令,实际命令要加单引号
sed [option] -f sed脚本文件 输入文件 使用sed脚本文件
sed脚本文件 [option] 输入文件 第一行具有sed命令解释器的sed脚本文件
option如下:
n 不打印; sed不写编辑行到标准输出,缺省为打印所有行(编辑和未编辑),p命令可以用来打印编辑行
c 下一命令是编辑命令,使用多项编辑时加入此选项
f 如果正在调用sed脚本文件,使用此选项,此选项通知sed一个脚本文件支持所用的sed命令,如
sed -f myscript.sed input_file 这里myscript.sed即为支持sed命令的文件
使用重定向文件即可保存sed的输出
使用sed在文本中定位文本的方式:
x x为一行号,比如1
x,y 表示行号范围从x到y,如2,5表示从第2行到第5行
/pattern/ 查询包含模式的行,如/disk/或/[a-z]/
/pattern/pattern/ 查询包含两个模式的行,如/disk/disks/
/pattern/,x 在给定行号上查询包含模式的行,如/disk/,3
x,/pattern/ 通过行号和模式查询匹配行,如 3,/disk/
x,y! 查询不包含指定行号x和y的行
基本sed编辑命令:
p 打印匹配行
c/ 用新文本替换定位文本
= 显示文件行号
s 使用替换模式替换相应模式
a/ 在定位行号后附加新文本信息
r 从另一个文本中读文本
i/ 在定位行号后插入新文本信息
w 写文本到一个文件
d 删除定位行
q 第一个模式匹配完成后退出或立即退出
l 显示与八进制ASCII代码等价的控制字符 y 传送字符
n 从另一个文本中读文本下一行,并附加在下一行 {} 在定位行执行的命令组
g 将模式2粘贴到/pattern n/
基本sed编程举例:
使用p(rint)显示行: sed -n ‘2p’ temp.txt 只显示第2行,使用选项n
打印范围: sed -n ‘1,3p’ temp.txt 打印第1行到第3行
打印模式: sed -n ‘/movie/‘p temp.txt 打印含movie的行
使用模式和行号查询: sed -n ‘3,/movie/‘p temp.txt 只在第3行查找movie并打印
显示整个文件: sed -n ‘1,$’p temp.txt $为最后一行
任意字符: sed -n ‘/.ing/‘p temp.txt 注意是.ing,而不是ing
打印行号: sed -e ‘/music/=’ temp.txt
附加文本:(创建sed脚本文件)chmod u+x script.sed,运行时./script.sed temp.txt
#!/bin/sed -f
/name1/ a/ #a/表示此处换行添加文本
HERE ADD NEW LINE. #添加的文本内容
插入文本: /name1/ a/ 改成 4 i/ 4表示行号,i插入
修改文本: /name1/ a/ 改成 /name1/ c/ 将修改整行,c修改
删除文本: sed ‘1d’ temp.txt 或者 sed ‘1,4d’ temp.txt
替换文本: sed ‘s/source/OKSTR/‘ temp.txt 将source替换成OKSTR
sed ‘s//$//g’ temp.txt 将文本中所有的$符号全部删除
sed ‘s/source/OKSTR/w temp2.txt’ temp.txt 将替换后的记录写入文件temp2.txt
替换修改字符串: sed ‘s/source/“ADD BEFORE” &/p’ temp.txt
结果将在source字符串前面加上”ADD BEFORE”,这里的&表示找到的source字符并保存
sed结果写入到文件: sed ‘1,2 w temp2.txt’ temp.txt
sed ‘/name/ w temp2.txt’ temp.txt
从文件中读文本: sed ‘/name/r temp2.txt’ temp.txt
在每列最后加文本: sed ‘s/[0-9]/& Pass/g’ temp.txt
从shell向sed传值: echo $NAME | sed “s/go/$REP/g” 注意需要使用双引号
快速一行命令:
‘s//.$//g’ 删除以句点结尾行
‘-e /abcd/d’ 删除包含abcd的行
‘s/[][][]/[]/g’ 删除一个以上空格,用一个空格代替
‘s/^[][]//g’ 删除行首空格
‘s//.[][]*/[]/g’ 删除句号后跟两个或更多的空格,用一个空格代替
‘/^$/d’ 删除空行
‘s/^.//g’ 删除第一个字符,区别 ‘s//.//g’删除所有的句点
‘s/COL/(…/)//g’ 删除紧跟COL的后三个字母
‘s/^////g’ 删除路径中第一个/
///////////////////////////////////////////////////////////////////////
、使用句点匹配单字符 句点“.”可以匹配任意单字符。“.”可以匹配字符串头,也可以是中间任意字符。假定正在过滤一个文本文件,对于一个有1 0个字符的脚本集,要求前4个字符之后为X C,匹配操作如下:. . . .X C. . . .
2、在行首以^匹配字符串或字符序列 ^只允许在一行的开始匹配字符或单词。在行首第4个字符为1,匹配操作表示为:^ . . . 1
3、在行尾以$匹配字符串或字符 可以说$与^正相反,它在行尾匹配字符串或字符, $符号放在匹配单词后。如果在行尾匹配单词j e t 0 1,操作如下:j e t 0 1 $ 如果只返回包含一个字符的行,操作如下:^ . $
4、使用*匹配字符串中的单字符或其重复序列 使用此特殊字符匹配任意字符或字符串的重复多次表达式。
5、使用/屏蔽一个特殊字符的含义 有时需要查找一些字符或字符串,而它们包含了系统指定为特殊字符的一个字符。如果要在正则表达式中匹配以* . p a s结尾的所有文件,可做如下操作:/ * / . p a s
6、使用[]匹配一个范围或集合 使用[ ]匹配特定字符串或字符串集,可以用逗号将括弧内要匹配的不同字符串分开,但并不强制要求这样做(一些系统提倡在复杂的表达式中使用逗号),这样做可以增 加模式的可读性。使用“ -”表示一个字符串范围,表明字符串范围从“ -”左边字符开始,到“ -”右边字符结束。假定要匹配任意一个数字,可以使用:[ 0 1 2 3 4 5 6 7 8 9 ] 要匹配任意字母,则使用:[ A - Z a - z ]表明从A - Z、a - z的字母范围。
7、使用/{/}匹配模式结果出现的次数 使用*可匹配所有匹配结果任意次,但如果只要指定次数,就应使用/ { / },此模式有三种形式,即:
pattern/{n/} 匹配模式出现n次。
pattern/{n,/} 匹配模式出现最少n次。
pattern/{n,m} 匹配模式出现n到m次之间,n , m为0 - 2 5 5中任意整数。
匹配字母A出现两次,并以B结尾,操作如下:A / { 2 / } B匹配值为A A B 匹配A至少4次,使用:A / { 4 , / } B
替换单引号为空:
可以这样写:
sed ‘s/‘“‘“‘//g’
sed ‘s/‘'‘//g’
sed s/'//g
1.1.78.5 示例
将/root/Desktop/testsed下的各个文件中的test1替换为test1-1
sed -i ‘s/test1/test1-1/g’ grep test1 -rl /root/Desktop/testsed
1.1.78.6 替换路径
echo “/home/”|sed “s:/home:/root:”
输出:/root/
1.1.78.7 应用
一、从第3000行开始,显示1000行。即显示3000~3999行
cat filename | tail -n +3000 | head -n 1000
二、显示1000行到3000行
cat filename| head -n 3000 | tail -n +1000
注意两种方法的顺序
分解:
tail -n 1000:显示最后1000行
tail -n +1000:从1000行开始显示,显示1000行以后的
head -n 1000:显示前面1000行
三、用sed命令
sed -n ‘5,10p’ filename 这样就可以只查看文件的第5行到第10行。
昨天写一个脚本花了一天的2/3的时间,而且大部分时间都耗在了sed命令上,今天不总结一下都对不起昨天流逝的时间啊~~~
用sed命令在行首或行尾添加字符的命令有以下几种:
假设处理的文本为test.file
在每行的头添加字符,比如”HEAD”,命令如下:
sed ‘s/^/HEAD&/g’ test.file
在每行的行尾添加字符,比如“TAIL”,命令如下:
sed ‘s/$/&TAIL/g’ test.file
运行结果如下图:
几点说明:
1.”^”代表行首,”$”代表行尾
2.’s/$/&TAIL/g’中的字符g代表每行出现的字符全部替换,如果想在特定字符处添加,g就有用了,否则只会替换每行第一个,而不继续往后找了
例:
3.如果想导出文件,在命令末尾加”> outfile_name”;如果想在原文件上更改,添加选项”-i”,如
4.也可以把两条命令和在一起,在test.file的每一行的行头和行尾分别添加字符”HEAD”、“TAIL”,命令:sed ‘/./{s/^/HEAD&/;s/$/&TAIL/}’ test.file
以上其实都还OK,昨天花太多时间,主要因为被处理的文件是用MySQL从数据库提取的结果导出来的,别人给我之后我就直接处理,太脑残了= -我一直有点怀疑之所以结果不对,有可能是windows和Linux换行的问题,可是因为对sed不熟,就一直在搞sed。。。。。。。
众所周知(= -),window和linux的回车换行之云云,如果你知道了,跳过这一段,不知道,读一下呗:
Unix系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<换行><回 车>”,即“\n\r”。一个直接后果是,Unix系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix下打开的话,在每行的结尾可能会多出一个^M符号。
好了,所以我的问题就出在被处理的文件的每行末尾都有^M符号,而这通常是看不出来的。可以用”cat -A test.file”命令查看。因此当我想在行尾添加字符的时候,它总是添加在行首且会覆盖掉原来行首的字符。
要把文件转换一下,有两种方法:
1.命令dos2unix test.file
2.去掉”\r” ,用命令sed -i ‘s/\r//‘ test.file
好了,这样处理完,就OK啦!!!
1.1.79 setup
设置系统参数
1.1.80 slabtop
slabtop命令以实时的方式显示内核“slab”缓冲区的细节信息。
1.1.81 ss
查看活动状态的套接字信息
1.1.82 strace
跟踪进程
1.1.83 strings
在对象文件或二进制文件中查找可打印的字符串
1.1.84 sync
刷新系统缓存
1.1.85 su/sudo
su切换用户
sudo以其他用户执行
1.1.86 tar
tar -cvzf test.tar.gz test/ 将当前目录下的test文件夹打包并压缩成gzip格式,
tar -xvzf test.tar.gz
x 解压文件 extract
c 打包文件 compress
v 显示详细过程 vision
z gzip,使用gzip压缩
j bz2格式
f 指定文件
-P 压缩时携带路径信息
tar -cvzf a.tar.gz -C / 压到指定目录用-C
tar cvPf a.tar.gz /opt/test
如果打包时带了路径,采用tar –xvPf解压时可恢复到对应路径
1.1.87 time
指定程序需要运行的时间
1.1.88 top
监视进程运行情况
-p 指定的进程
top –p 123,134
1.1.89 touch
新建指定名称的文件或更新文件时间戳
1.1.90 tr
将字符进行替换或删除
1.1.91 tree
列出目录树
-a 列出所有文件
-d 只列出目录
-f 列出文件的完整目录
-I 去掉输出中的导引线
-L level 只列出到第level层子目录
tree /boot/ -p *.conf 在..目录下,以.conf结尾的文件以树状显示
1.1.92 wall
向当前机器所有用户广播信息
wall “hello”
1.1.93 write
向某个用户发送消息
write hytera(用户)
hello
1.1.94 whatis
查询某个条目出现在手册的哪些节中。
1.1.95 which
查找程序所在位置
如:which ls /usr/bin/ls
1.1.96 wc
查看文件信息命令
文件的文本行数 文件的字数 文件的字符数
1.1.97 xxd
读取二进制文件,以二进制显示
Hexdump 以16进制显示
1.1.98 yum
YUM 软件包升级器 - (Fedora, RedHat及类似系统)
yum install package_name 下载并安装一个rpm包
yum -Y install gcc-c++ 安装gcc-c++
-Y:不需要用户确认发生的动作
1.1.99 zip/uzip
zip jack jack.doc
uzip jack .zip
1.1.100 uname
显示系统信息
-a 显示所有信息
-r 显示内核版本信息
-m 机器硬件名称
1.1.101 uptime
显示Linux负载信息
1.1.102 useradd
useradd smart 添加smart用户
1.1.103 vmstat命令
vmstat命令的含义为显示虚拟内存状态(“Viryual Memor Statics”),但是它可以报告关于进程、内存、I/O等系统整体运行状态。
1.1.103.1 语法
vmstat(选项)(参数)
1.1.103.2 选项
-a:显示活动内页;
-f:显示启动后创建的进程总数;
-m:显示slab信息;
-n:头信息仅显示一次;
-s:以表格方式显示事件计数器和内存状态;
-d:报告磁盘状态;
-p:显示指定的硬盘分区状态;
-S:输出信息的单位。
1.1.103.3 参数
Ø 事件间隔:状态信息刷新的时间间隔;
Ø 次数:显示报告的次数。
1.1.103.4 实例
vmstat 3
procs ———–memory———- —swap– —–io—- –system– —–cpu——
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 320 42188 167332 1534368 0 0 4 7 1 0 0 0 99 0 0
0 0 320 42188 167332 1534392 0 0 0 0 1002 39 0 0 100 0 0
0 0 320 42188 167336 1534392 0 0 0 19 1002 44 0 0 100 0 0
0 0 320 42188 167336 1534392 0 0 0 0 1002 41 0 0 100 0 0
0 0 320 42188 167336 1534392 0 0 0 0 1002 41 0 0 100 0 0
1.1.103.5 字段说明:
Procs(进程)
· r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1)
· b: 等待IO的进程数量。
Memory(内存)
· swpd: 使用虚拟内存大小,如果swpd的值不为0,但是SI,SO的值长期为0,这种情况不会影响系统性能。
· free: 空闲物理内存大小。
· buff: 用作缓冲的内存大小。
· cache: 用作缓存的内存大小,如果cache的值大的时候,说明cache处的文件数多,如果频繁访问到的文件都能被cache处,那么磁盘的读IO bi会非常小。
Swap
· si: 每秒从交换区写到内存的大小,由磁盘调入内存。
· so: 每秒写入交换区的内存大小,由内存调入磁盘。
注意:内存够用的时候,这2个值都是0,如果这2个值长期大于0时,系统性能会受到影响,磁盘IO和CPU资源都会被消耗。有些朋友看到空闲内存(free)很少的或接近于0时,就认为内存不够用了,不能光看这一点,还要结合si和so,如果free很少,但是si和so也很少(大多时候是0),那么不用担心,系统性能这时不会受到影响的。
IO(现在的Linux版本块的大小为1kb)
· bi: 每秒读取的块数
· bo: 每秒写入的块数
注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。
system(系统)
· in: 每秒中断数,包括时钟中断。
· cs: 每秒上下文切换数。
注意:上面2个值越大,会看到由内核消耗的CPU时间会越大。
CPU(以百分比表示)
· us: 用户进程执行时间百分比(user time)
us的值比较高时,说明用户进程消耗的CPU时间多,但是如果长期超50%的使用,那么我们就该考虑优化程序算法或者进行加速。
· sy: 内核系统进程执行时间百分比(system time)
sy的值高时,说明系统内核消耗的CPU资源多,这并不是良性表现,我们应该检查原因。
· wa: IO等待时间百分比
wa的值高时,说明IO等待比较严重,这可能由于磁盘大量作随机访问造成,也有可能磁盘出现瓶颈(块操作)。
· id: 空闲时间百分比
1.1.104 排除某个目录或文件
$:tree test
test
├── a
├── b
│ └── c
├── c
└── d
tar –cvzf test.tar.gz –exclude={test/b,test/c} test
1.1.105 删除除某个文件外的文件
rm –rf ls|grep –v “hello.c”
1.1.106 Linux反选删除文件汇总
最简单的方法是
# shopt -s extglob (打开extglob模式)
# rm -fr !(file1)
如果是多个要排除的,可以这样:
# rm -rf !(file1|file2)
Linuxrm删除指定文件外的其他文件方法汇总
一、Linux下删除文件和文件夹常用命令如下: 删除文件: rm file
删除文件夹: rm -rf dir
需要注意的是, rmdir 只能够删除 空文件夹。 二、删除制定文件(夹)之外的所有文件呢?
1、方法1,比较麻烦的做法是:
复制需要保留的文件到其他文件夹,然后将该目录删除, 然后将需要保留的移动 回来。 mv keep ../ #保留文件(夹) keep
rm -rf * #删除当前文件夹里的所有文件 mv ../keep ./ #将原来的东西移动回来
2、方法2,需要在当前文件夹中进行:
rm -rf !(keep) #删除keep文件之外的所有文件
rm -rf !(keep1 | keep2) #删除keep1和keep2文件之外的所有文件
3、方法3,当前文件夹中结合使用grep和xargs来处理文件名: ls | grep -v keep | xargs rm #删除keep文件之外的所有文件
说明: ls先得到当前的所有文件和文件夹的名字, grep -v keep,进行grep正则匹配查找keep,-v参数决定了结果为匹配之外的结果,也就是的到了keep之外的所有文件名,然后 xargs用于从 标准输入获得参数 并且传递给后面的命令,这里使用的命令是 rm,然后由rm删除前面选择的文件。
好处:使用了grep来正则表达式来匹配文件名字,可以一次保留多个文件,从而进行更加准确的处理。
4、方法4,使用find命令代替ls,改进方法3从而能够处理制定文件夹的文件:
find ./test/ | grep -v keep | xargs rm #删除当前test文件夹中keep文件之外的所有文件
说明,用grep而不用find -name选取名字,因为find选取名字时比较麻烦,对正则表达式支持不够,无法排除指定文件名。
5、方法5,直接使用find命令删除其他文件:
find ./ -name ‘[^k][^e][^e][^p]‘ -exec rm -rf {} ; #删除keep以外的其他文件。 find ./ -name ‘[^k][^e][^e][^p]‘ | xargs rm -rf #删除keep以外的其他文件。推荐!
说明:上面第二行的代码效率高些,原因在于删除多个文件时 -exec会启动多个进程来处理,而xargs会启动一个rm进程来处理。
关于find 执行命令的效率和借用xargs启动的命令效率的比较,详情请参考:http://www.linuxsir.org/main/?q=node/137
Linux文件查找命令find,xargs详述 http://www.linuxsir.org/main/?q=node/137 本文转载自:http://blog.sina.com.cn/s/blog_70ffb5c501011rrk.html
rm删除除去指定文件的剩余所有文件 (rm 反向删除)
zhou@zhou:/LinuxC/file/test$ ls/LinuxC/file/test$
1 23sdfwe 88888888 aabb ag ghdda mmm
2 3 aaaaaaaa abc asdg llllllll wwwww
zhou@zhou:
然后我想删除除了包含字符串aa外所有的文件,也就是想留下aabb,aaaaaaaa,这两个文件,其他的全部删除
下面是我的命令:
zhou@zhou:/LinuxC/file/test$ rm /LinuxC/file/test$ lsls | grep -v"aa"
zhou@zhou:
aaaaaaaa aabb
zhou@zhou:~/LinuxC/file/test$
所以了,成功了。
简单的解释一下那条命令吧:rm 删除后面指定的文件ls | grep -v "aa"
记得外面是反引号(反引号的位置就在标准键盘的数字1的左边),
ls:查看当前目录下所有的文件,使用grep命令过滤一下grep -v “aa” 就是找出字符串中不带“aa”的。
整体再顺一下:列出文件名不带“aa”串的文件,然后删除他们。OK。
其实说起来简单,当时我也做了好长时间,因为以前没怎么接触grep,因此一开始我想到的办法是使用正则表****达式,但是在做的过程中突然发现了grep使个不错的东西,因此就使用了。
上面的命令使删除带有“aa”串的文件,那如果我只想留下文件aa呢?很简单
zhou@zhou:~/LinuxC/file/test$rm ls | grep -v"^aa$"
在aa前面加上^,后面加上$表示结束符的意思,这个就是完全匹配了。
好了,就这么多。但愿以后能用到这个有用的命令
转自:http://blog.sina.com.cn/s/blog_67e34ceb01014930.html
linux 删除其他文件
求教 linux centos我想删除某目录中文件名不符合”20100330“这样规则的文件应该怎么删除?
假设目录名为myTest且为当前目录的下一级目录,使用如下命令即可:
cd ./myTest && rm ls | grep -v '20100330'
&& cd ..
进入myTest目录,删除文件后返回当前目录。
grep的-v参数表示反向选择。
一般使用rm删除文件的时候会有确认提示,如果不要确认,直接强制删除,可以使用rm的-f参数。
其他1条回答
find ./ -type f ! -name “20100330“ -exec rm -rf {} ;
如何反向选择文件并删除
http://bbs.csdn[**.NET**](http://lib.csdn.net/base/dotnet)/topics/390077765
案例:一个文件夹下我想删除 除了abc文件 之外的所有文件,命令怎么写(Linux下)
find . -maxdepth 1 -type f -not -name ‘abc’ -exec rm ‘{}’ ‘;’
ls | grep -v abc | xargs -i rm -rf {}
rm -f ls | grep -v abc
如果文件很多的情况下,不要用这种方法……
要用2楼的方法……
mv abc /tmp
rm *
mv /tmp/abc .
反向显示文件
使用ls命令仅仅显示当前目录不包括.的文件.
ls加grep过滤的方式:ls -al | grep -v ‘.‘(ls –ignore=.* -al也可实现)。
在打开extglob模式下(缺省是打开的),ls也可以实现,而且更加灵活.
shopt -u extglob #关闭
shopt -s extglob #打开
ls -al !(.)
ls -al -d !(.)
1.2 Linux常用命令
1.2.1 系统信息
arch 显示机器的处理器架构(1)
cat /proc/version 显示内核的版本
cat /proc/net/dev 显示网络适配器及统计
cat /proc/mounts 显示已加载的文件系统
date 显示系统日期
cal 2007 显示2007年的日历表
date 041217002007.00 设置日期和时间 - 月日时分年.秒
date –s “20180410”
clock -w 将时间修改保存到 BIOS
shutdown -h now 关闭系统(1)
reboot 重启(2)
logout 注销
1.2.2 文件和目录
cd /home 进入 ‘/ home’ 目录’
cd .. 返回上一级目录
cd ../.. 返回上两级目录
cd 进入个人的主目录
cd ~user1 进入个人的主目录
cd - 返回上次所在的目录
pwd 显示工作路径
ls 查看目录中的文件
ls -F 查看目录中的文件
ls -l 显示文件和目录的详细资料
ls -a 显示隐藏文件
ls [0-9] 显示包含数字的文件名和目录名
tree 显示文件和目录由根目录开始的树形结构(1)
lstree 显示文件和目录由根目录开始的树形结构(2)
mkdir dir1 创建一个叫做 ‘dir1’ 的目录’
mkdir dir1 dir2 同时创建两个目录
mkdir -p /tmp/dir1/dir2 创建一个目录树
rm -f file1 删除一个叫做 ‘file1’ 的文件’
rmdir dir1 删除一个叫做 ‘dir1’ 的目录’
rm -rf dir1 删除一个叫做 ‘dir1’ 的目录并同时删除其内容
rm -rf dir1 dir2 同时删除两个目录及它们的内容
mv dir1 new_dir 重命名/移动 一个目录
cp file1 file2 复制一个文件
cp dir/* . 复制一个目录下的所有文件到当前工作目录
cp -a /tmp/dir1 . 复制一个目录到当前工作目录
cp -a dir1 dir2 复制一个目录
ln -s file1 lnk1 创建一个指向文件或目录的软链接
ln file1 lnk1 创建一个指向文件或目录的物理链接
1.2.3 文件搜索
find / -name file1 从 ‘/‘ 开始进入根文件系统搜索文件和目录
find /home/user1 -name *.bin 在目录 ‘/ home/user1’ 中搜索带有’.bin’ 结尾的文件
find / -name *.rpm -exec chmod 755 ‘{}’ ; 搜索以 ‘.rpm’ 结尾的文件并定义其权限
locate *.ps 寻找以 ‘.ps’ 结尾的文件 - 先运行 ‘updatedb’ 命令
whereis halt 显示一个二进制文件、源码或man的位置
which halt 显示一个二进制文件或可执行文件的完整路径
1.2.4 挂载一个文件系统
mount /dev/hda2 /mnt/hda2 挂载一个叫做hda2的盘 - 确定目录 ‘/ mnt/hda2’ 已经存在
umount /dev/hda2 卸载一个叫做hda2的盘 - 先从挂载点 ‘/ mnt/hda2’ 退出
fuser -km /mnt/hda2 当设备繁忙时强制卸载
umount -n /mnt/hda2 运行卸载操作而不写入 /etc/mtab 文件- 当文件为只读或当磁盘写满时非常有用
mount -o loop file.iso /mnt/cdrom 挂载一个文件或ISO镜像文件
mount -t vfat /dev/hda5 /mnt/hda5 挂载一个Windows FAT32文件系统
mount /dev/sda1 /mnt/usbdisk 挂载一个usb 捷盘或闪存设备
mount -t smbfs -o username=user,password=pass //WinClient/share /mnt/share 挂载一个windows网络共享
passwd 修改口令
passwd user1 修改一个用户的口令 (只允许root执行)
1.2.5 打包和压缩文件
tar -cvfj archive.tar.bz2 dir1 创建一个bzip2格式的压缩包
tar -xvfj archive.tar.bz2 解压一个bzip2格式的压缩包
tar -cvfz archive.tar.gz dir1 创建一个gzip格式的压缩包
tar -xvfz archive.tar.gz 解压一个gzip格式的压缩包
zip file1.zip file1 创建一个zip格式的压缩包
unzip file1.zip 解压一个zip格式压缩包
1.2.6 如何设置Linux启动模式
把/etc/inittab中的默认启动级别设为3
七个运行级别分别是:
0:直接关机
1:单用户模式
2:没有NFS服务
3:完整含有网络功能的纯文本模式
4:系统保留功能
5:图形界面
6:重启
1.2.7 linux下解决端口被占用问题
查找被占用的端口:
netstat -tln
netstat -tln | grep 8080
查看端口属于哪个程序
lsof -i :8080
杀掉占用端口的进程:
kill -9 进程ID
ps -ef | grep xxx
kill xxx
1.2.8 修改core文件格式
永久修改:sysctl -w kernel.core_pattern=%e.core.%p
临时修改:
1.2.9 释放cache缓存
echo 3>/proc/sys/vm/drop_caches
1.3 得到系统bit位
getconf LONG_BIT
1.4 系统设置
1.4.1 Linux防火墙(iptables)的开启与关闭
Linux中的防火墙主要是对iptables的设置和管理。
① Linux防火墙(Iptables)重启系统生效
开启: chkconfig iptables on
关闭: chkconfig iptables off
② Linux防火墙(Iptables) 即时生效,重启后失效
开启: service iptables start
关闭: service iptables stop
③ 示例
在开启了Linux防火墙(Iptables)时,做如下设置,开启25和110端口,修改/etc/sysconfig/iptables 文件,添加以下内容:
-A RH-Firewall-1-INPUT -m state –state NEW -p tcp -m tcp –dport 25 –syn -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -p tcp -m tcp –dport 110 –syn -j ACCEPT
1.5 系统服务
1.5.1 rsyslog
配置文件:/etc/rsyslog.conf
$SystemLogRateLimitInterval 0
local6.debug -/workspace/im.log
日志限定大小:
#Update the /etc/crontab for running the logrotate every 5 minutes
var=grep -c "/usr/sbin/logrotate /etc/logrotateim.conf" /etc/crontab
if [ $var == 0 ] ; then
echo “*/2 * * * * root /usr/sbin/logrotate /etc/logrotateim.conf “ >> /etc/crontab
fi
logrotateim.conf内容:
# see “man logrotate” for details
# rotate log files weekly
weekly
# keep 4 weeks worth of backlogs
rotate 4
# create new (empty) log files after rotating old ones
create
# use date as a suffix of the rotated file
#dateext
# uncomment this if you want your log files compressed
#compress
# RPM packages drop log rotation information into this directory
include /etc/logrotate.d
# no packages own wtmp and btmp – we’ll rotate them here
/var/log/wtmp {
monthly
create 0664 root utmp
minsize 1M
rotate 1
}
# system-specific logs may be also be configured here.
/var/log/im/im.log {
rotate 10
size=100M
postrotate
/usr/bin/kill -HUP rsyslogd
endscript
}
/var/log/im/memery.log {
rotate 10
size=100M
postrotate
/usr/bin/kill -HUP rsyslogd #注意此处可能为syslogd
endscript
}
# system-specific logs may be also be configured here.
1.5.2 crond
定时任务
配置文件:/etc/crontab
1.5.3 开机自动执行脚本
① 将脚本置于/etc/rc.local中即可
② /etc/init.d/rcS
1.5.4 设置时间
显示时间 hwclock
设置时间 在linux系统设置系统时钟用命令date,格式为:date 062920502008.10,表示系统时间设置为2008年6月29日20时50分10秒。硬件时钟RTC时间是通过hwclock命令来设置的,比如说硬件时间要设置为2008年6月29日20时50分10秒,则应该先用date 062920502008.10,然后用命令:hwclock -w,这样RTC时间就跟系统时间一致了。
http://blog.163.com/pirates_fish/blog/static/183333150201141222953793/
http://blog.csdn.net/jk110333/article/details/8590746
1.6 系统操作
1.7 重定向
数字 | 含义 | 标准叫法 |
---|---|---|
0 | 标准输入 | stdin = standard input |
1 | 标准输出 | stdout = standard output |
2 | 标准错误输出 | stderr = standard error |
而系统默认的stdin,stdout,stderr,都是屏幕,所以,当你执行命令,比如make,后,所输出的信息,都是可以在屏幕上看到的。所以,想要将对应信息输出到某个文件中,就用对应的数字加上重定向符号’>’,实现将这些信息,重新定向到对应的文件中,即可。下面以make命令为例来说明,如何把对应的信息,输出到对应的文件中:
1.想要把make输出的全部信息,输出到某个文件中,最常见的办法就是:
make xxx > build_output.txt
此时默认情况是没有改变2=stderr的输出方式,还是屏幕,所以,如果有错误信息,还是可以在屏幕上看到的。
2.只需要把make输出中的错误(及警告)信息输出到文件中ing,可以用:
make xxx 2> build_output.txt****
相应地,由于1=stdout没有变,还是屏幕,所以,那些命令执行时候输出的正常信息,还是会输出到屏幕上,你还是可以在屏幕上看到的。
3.只需要把make输出中的正常(非错误,非警告)的信息输出到文件中,可以用:
**make xxx **1> build_output.txt
相应地,由于2=stderr没有变,还是屏幕,所以,那些命令执行时候输出的错误信息,还是会输出到屏幕上,你还是可以在屏幕上看到的。
4.想要把正常输出信息和错误信息输出到分别的文件中,可以用:
**make xxx 1> build_output_normal.txt **2>build_output_error.txt
即联合使用了1和2,正常信息和错误信息,都输出到对应文件中了。
\5. 所有的信息都输出到同一个文件中:
make xxx > build_output_all.txt 2>&1
其中的2>&1表示错误信息输出到&1中,而&1,指的是前面的那个文件:build_output_all.txt 。
注意:上面所有的1,2等数字,后面紧跟着大于号’>’ ,中间不能有空格。****
1.8 关于文本导出
1.8.0.1 如何把命令运行的结果保存到文件当中?
用 >
例子:
[lhd@hongdi ~]$ ls > ls.txt
[lhd@hongdi ~]$ cat ls.txt
1.gtkrc-2.0
2009
a
amsn_received
a.tar.gz
说明: > 是把输出转向到指定的文件,如文件已存在的话也会重新写入,文件原内容不会保留
是把输出附向到文件的后面,文件原内容会保留下来
1.8.0.2 如何能在输出信息的同时把信息记录到文件中?
tee
tee的作用:read from standard input and write to standard output and files
它从标准输入读取内容并将其写到标准输出和文件中
看例子:
[lhd@hongdi ~]$ ls | tee ls_tee.txt
1.gtkrc-2.0
2009
a
amsn_received
a.tar.gz
[lhd@hongdi ~]$ cat ls_tee.txt
1.gtkrc-2.0
2009
a
amsn_received
a.tar.gz
备注:使用 tee时,如果想保留目标文件原有的内容怎么办?
可以使用 -a参数
-a, –append
append to the given FILEs, do not overwrite
附加至给出的文件,而不是覆盖它
1.8.0.3 同时在屏幕和文件中输出
./build 2>&1 | tee build.log
1.8.0.4 多个命令的输出都需要记录,可以用script
script这个命令很强大,可以记录终端的所有输出到相应的文件中
看例子:
[lhd@hongdi ~]$ script
Script. started, file is typescript
[lhd@hongdi ~]$ ls
1.gtkrc-2.0 c.tar kmess-2.0alpha2.tar.gz secpanel-0.5.3-1.noarch.rpm
2009 DownZipAction.php kmesslog secpanel-0.5.4-2.noarch.rpm
[lhd@hongdi ~]$ exit
exit
Script. done, file is typescript
[lhd@hongdi ~]$ cat typescript
Script. started on 2009年02月08日 星期日 18时56分52秒
[lhd@hongdi ~]$ ls
1.gtkrc-2.0 c.tar kmess-2.0alpha2.tar.gz secpanel-0.5.3-1.noarch.rpm
2009 DownZipAction.php kmesslog secpanel-0.5.4-2.noarch.rpm
[lhd@hongdi ~]$ exit
exit
Script. done on 2009年02月08日 星期日 18时57分00秒
说明:
1,我们在启动script时没有指定文件名,它会自动记录到当前目录下一个名为 typescript的文件中。也可以用 -a参数 指定文件名
例子:
[lhd@hongdi ~]$ script. -a example.txt
Script. started, file is example.txt
此时终端的输出内容被记录到 example.txt这个文件中
2,退出script时,用exit
感到奇怪吗?事实上script就是启动了一个shell
看一下ps auxfww 的信息就知道了
lhd 17738 0.1 3.2 152028 33328 ? Sl 18:30 0:03 /usr/bin/konsole
lhd 17740 0.0 0.1 6372 1720 pts/1 Ss 18:30 0:00 _ /bin/bash
lhd 17900 0.0 0.0 5344 628 pts/1 S 19:01 0:00 | _ script
lhd 17901 0.0 0.0 5348 464 pts/1 S 19:01 0:00 | _ script
lhd 17902 0.5 0.1 6372 1688 pts/2 Ss 19:01 0:00 | _ bash -i
3,查看typescript的内容,可以看到它同时记录下了script的启动和结束时间
用script录制并播放session的内容
我们可以用 script把整个终端会话的所有操作和输出录制下来,然后再用scriptreplay进行播放。
如果录制时记录下来了操作时的时间数据,那么播放时和操作时的使用时间完全相同。
这个很有用吧,比如:我们可以把安装软件时编译的过程记录下来,然后给别人进行演示
看例子:
[lhd@hongdi ~]$ script. -t 2>example.time -a example.txt
Script. started, file is example.txt
[lhd@hongdi ~]$ ls
说明: -t 2>example.time -t是把时间数据输出到标准错误(standard error),所以我们使用 2>example.time 把数据转向到 example.time这个文件当中
如何播放所记录的内容?
第一步:安装scriptreplay
下载
wget linux/utils/util-linux/util-linux-2.12r.tar.bz2”>ftp://ftp.kernel.org/pub/linux/utils/util-linux/util-linux-2.12r.tar.bz2
解压
tar -jxvf util-linux-2.12r.tar.bz2
之后复制文件到系统的命令目录中即可
[root@hongdi 下载]# cp util-linux-2.12r/misc-utils/scriptreplay.pl /usr/bin/scriptreplay
[root@hongdi 下载]# chmod 755 /usr/bin/scriptreplay
备注: fedora 10的util-linux-ng-2.14.1-3.2.fc10.i386.rpm 此包中已包含 scriptreplay,已无需另行安装
第二步:播放所录制的session内容
[lhd@hongdi ~]$ scriptreplay example1.time example1.txt
[lhd@hongdi ~]$ ls
1.gtkrc-2.0 c.tar jeffray_lee@hotmail.com pass
[lhd@hongdi ~]$ abcd
bash: abcd: command not found
[lhd@hongdi ~]$ exit
1.9 Linux打开单层文件夹
把Always open in browse windows 勾选。
1.10 Linux打开文件夹后默认最大化
如图。
1.11 Linux下采用Cloc统计代码行数
源代码下载、详细说明等见:http://cloc.sourceforge.net
1.11.1 使用方法
1.11.1.1 显示各类文件统计结果
Cloc . :统计当前文件夹下的所有文件的代码
统计结果如下图,
1.11.1.2 详细统计各个文件下的代码量
首先安装好sqlite3.
命令:cloc –sql 1 .|sqlite3 code.db
Cloc –sql 1 –sql-project epc /epc地址/|sqlite3 epc.db
说明:.表示当前目录,也可以为压缩文件等。
执行后,统计结果将输出到code.db数据库中,可以使用工具导出。
当有多个工程需要统计时可以使用:
cloc –sql –sql-project pgw –sql-append /pgw地址|sqlite3 epc.db 将后面的数据附加到前面的数据库
1.12 永久性修改hostname
/etc/sysconfig/network
HOSTNAME = smart
重启即可生效
1.13 查看linux发布版本
cat /etc/redhat-release
1.14 修改ip地址
对于开发板:
vi /etc/eth0-setting
对于CentOS:
修改/etc/sysconfig/network-scripts/
1.15 去除DHCP
修改启动文件/etc/init.d/rcS 把 fa-network-service注释掉
1.16 telnet
远程登录时,应注意新版的文件系统必须先设置开发板的密码,方可远程登录。
passwd root
1.17 修改主机名
1.17.1 方法1
主机名即:root@zhuyan中的zhuyan
hostname DataCollector即修改了。
1.17.2 方法2
修改rcS 下的/bin/hostname
1.18 linux配置虚拟IP地址
在日常linux管理工作中,需要为应用配置单独的IP地址,以达到主机与应用的分离,在应用切换与迁移过程中可以做到动态切换,特别是在使用HA的时候,这种方案可以保证主机与应用的隔离,对日常的运维有很大的益处.
但在有些应用中还没有配置HA,后期需要配置HA时,我们可以先配置虚拟IP给在线的应用使用,这要后期的系统运维可以做到更好的可扩展性.
本文主要是对IP地址如何配置做一些简单的介绍.
PS:以下实例在redhat linux实施成功!
- 在线配置虚拟IP
ifconfig eth0:1 192.168.109.108 netmask 255.255.255.0
- 查看新增加的子接口配置信息是否正确.主要是看IP与子网掩码.
ifconfig eth0:1
# ifconfig eth0:1
eth0:1 Link encap:Ethernet HWaddr 00:0C:29:45:62:3B
inet addr:192.168.109.108 Bcast:192.168.109.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
Base address:0x2000 Memory:d8920000-d8940000
- 启动网卡eth0的子接口
ifconfig eht0:1 up
查看IP是否可以联通
ping -c 3 192.168.109.108
# ping -c 3 192.168.109.108
PING 192.168.109.108 (192.168.109.108) 56(84) bytes of data.
64 bytes from 192.168.109.108: icmp_seq=1 ttl=64 time=0.032 ms
64 bytes from 192.168.109.108: icmp_seq=2 ttl=64 time=0.053 ms
64 bytes from 192.168.109.108: icmp_seq=3 ttl=64 time=0.036 ms
— 192.168.109.108 ping statistics —
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.032/0.040/0.053/0.010 ms
- 修改配置文件,使配置在主机重启后自动生效.
cd /etc/sysconfig/network-script/
cp ifcfg-eth0 ifcfg-eth0:1
vi ifcfg-eth0:1
# Intel Corporation 82545EM Gigabit Ethernet Controller (Copper)
DEVICE=eth0:1 —->子接口名
HWADDR=00:0C:29:45:62:3B
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.109.108
NETMASK=255.255.255.0
- 对于要切换主机IP与应用IP的情可以做如下处理
如下面的例子:主机IP是192.168.109.105 应用IP是192.168.109.108
我们要将IP做对调,
192.168.109.108给主机使用
192.168.109.105给应用使用
做法如下:
配置前的信息:
#ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:0C:29:45:62:3B
inet addr:192.168.109.105 Bcast:192.168.109.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe45:623b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:20340 errors:0 dropped:0 overruns:0 frame:0
TX packets:16678 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1800154 (1.7 MiB) TX bytes:2438822 (2.3 MiB)
Base address:0x2000 Memory:d8920000-d8940000
# ifconfig eth0:1
eth0:1 Link encap:Ethernet HWaddr 00:0C:29:45:62:3B
inet addr:192.168.109.108 Bcast:192.168.109.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
Base address:0x2000 Memory:d8920000-d8940000
修改eth0:1的网卡配置信息:
# vi ifcfg-eth0:1
# Intel Corporation 82545EM Gigabit Ethernet Controller (Copper)
DEVICE=eth0:1 —->子接口名
HWADDR=00:0C:29:45:62:3B
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.109.105
NETMASK=255.255.255.0
修改eth0的网卡配置信息:
#vi ifcfg-eth0
# Intel Corporation 82545EM Gigabit Ethernet Controller (Copper)
DEVICE=eth0
HWADDR=00:0C:29:45:62:3B
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.109.108
NETMASK=255.255.255.0
重启网络生效:这一步很重要!!!
nohup service network restart &
将命令提交给后台去执行,这样子可以保证系统正常重启网络
1.19 Linux设备
/dev/pts是远程登陆(telnet**,ssh等)后创建的控制台设备文件所在的目录。第一个用户登陆,console的设备文件为/dev/pts/0,第二个为/dev/pts/1,以此类推。这里的0、1、2、3不是具体的标准输入或输出,**而是整个控制台。你可尝试 echo “aaaaaa” > /dev/pts0、1、2……。
1.20 Samba搭建
安装Samba软件
编辑/etc/samba/smb.conf
[root]
comment=Root Directories
browseable=yes
path= /root/
valid users =root
writable=yes
smbpasswd –a root
重启服务
主机打开\192.168.55.21 ok
1.21 Linux软件打包
1.21.1 Linux打包软件
1.21.1.1 rpmdevtools
首先安装rpmdevtools这个工具,该工具包含rpmbuild,rpmdev-newspec,rpmdev-setuptree等工具
sudo yum install rpmdevtools
rpm常用命令
安装/升级命令
rpm -ivh package_name
rpm -Uvh package_name
rpm -Fvh package_name
选项与参数:
-i: install 的意思
-v: 察看更绅部的安装信息画面
-h:以安装信息列显示安装迚度
-U: 升级软件,如果系统未安装指定软件,则进行安装
-F: 升级指定的软件,如果该软件未安装,则不执行任何操作
卸载
rpm -e package_name
强制卸载
rpm -e –nodeps package_name
重建rpm数据库
rpm –rebuilddb
查询某个软件是否安装
rpm -qa|grep package_name
列出完成的安装信息
rpm -ql packa_name
1.21.1.2 rpmbuild
# 同时产生rpm和srpm包
rpmbuild -ba spec
#只产生rpm包
rpmbuild -bb spec
# 用srpm包生成rpm包
rpmbuild –rebuild src.rpm
rpmdev-setuptree
在CentOS 6.2系统下,使用该命令,会生成~/rpmbuild目录.
其子目录功能:
目录名 解释
BUILD 解压SOURCES目录中的tar.gz源码包后,放到该目录下
BUILDROOT 执行make install时的”虚拟”目录
RPMS 打包好的rpm文件
SOURCES 源码包(tar.gz)
SPECS spec配置文件
SRPMS 打包好的src.rpm文件
1.21.1.3 rpmdev-newspec
在当前目录下生成spec模板文件,默认文件名称:newpackage.spec
1.21.1.4 rpmdev-newinit
生成service初始化文件
1.21.2 打包步骤
常见的Linux发行版主要可以分为两类,类ReadHat系列和类Debian系列,这里我们是以其软件包的格式来划分的,这两类系统分别提供了自己的软件包管理系统和相应的工具。类RedHat系统中软件包的后缀是rpm;类Debian系统中软件包的后缀是deb。另一方面,类RedHat系统提供了同名的rpm命令来安装、卸载、升级rpm软件包;类Debian系统同样提供了dpkg命令来对后缀是deb的软件包进行安装、卸载和升级等操作。
rpm的全称是Redhat Package Manager,常见的使用rpm软件包的系统主要有Fedora、CentOS、openSUSE、SUSE企业版、PCLinuxOS以及Mandriva Linux、Mageia等。使用deb软件包后缀的类Debian系统最常见的有Debian、Ubuntu、Finnix等。
无论是rpm命令还是dpkg命令在安装软件包时都存在一个让人非常头疼的问题,那就是软件包的依赖关系。这一点很多人应该深有体会,这也使初学者在接触Linux系统时觉得很不方便的地方。庆幸的是,很多发行版都考虑到了这问题,于是Fedora和CentOS提供了yum来自动解决软件包的安装依赖,同样的openSUSE提供了zypper,类Debian系统提供了apt-*命令。也就是说这些工具本质上最终还是调用了rpm(或者dpkg)是不过安装前自动帮用户解决了软件包的安装依赖。如下表所示:
分类 | 发行版 | 手动安装命令 | 自动安装命令 | 软件包后缀 |
---|---|---|---|---|
类RedHat | Fedora/CentOS | rpm | yum | *.rpm |
openSUSE/SUSE | zypper | |||
Mandriva Linux/Mageia | urpmi | |||
类Debian | Debian/Ubuntu | dpkg | apt-get | *.deb |
简单点了说,如果你会在Fedora或者CentOS上用yum来自动安装软件包,那么在Debian或者Ubuntu上你就会用apt-get自动安装软件,同理,在openSUSE上你就会用zypper自动安装软件包。
本文档主要描述如何通过软件包的源代码构建自己的rpm软件安装包。
从软件运行的结构来说,一个软件主要可以分为三个部分:可执行程序、配置文件和动态库。当然还有可能会有相关文档、手册、供二次开发用的头文件以及一些示例程序等等。其他部分都是可选的,只有可执行文件是必须的。
关于如何制作rpm软件包的方法,网上教程也一大堆,谈及最多的当属rpmbuild这个命令行工具。这也是本文要介绍的“配角”,而主角是它的输入对象,也就是所谓的SPEC文件的格式,写法和说明。
rpm的打包流程相比deb的要稍微麻烦一些,因为它对打包目录有一些严格的层次上的要求。如果你的rpm的版本<=4.4.x,那么rpmbuid工具其默认的工作路径是/usr/src/redhat,这就使得普通用户不能制作rpm包,因为权限的问题,在制作rpm软件包时必须切换到root身份才可以。所以,rpm从4.5.x版本开始,将rpmbuid的默认工作路径移动到用户家目录下的rpmbuild目录里,即$HOME/rpmbuild,并且推荐用户在制作rpm软件包时尽量不要以root身份进行操作。
关于rpmbuild默认工作路径的确定,通常由在/usr/lib/rpm/macros这个文件里的一个叫做**%_topdir**的宏变量来定义。如果用户想更改这个目录名,rpm官方并不推荐直接更改这个目录,而是在用户家目录下建立一个名为.rpmmacros的隐藏文件(注意前面的点不能少,这是Linux下隐藏文件的常识),然后在里面重新定义%_topdir,指向一个新的目录名。这样就可以满足某些“高级”用户的差异化需求了。通常情况下.rpmmacros文件里一般只有一行内容,比如:
*%_topdir $HOME/myrpmbuildenv ** **** 在%_topdir目录下一般需要建立6个目录: ***
目录名 | 说明 | macros中的宏名 |
---|---|---|
BUILD | 编译rpm包的临时目录 | %_builddir |
BUILDROOT | 编译后生成的软件临时安装目录 | %_buildrootdir |
RPMS | 最终生成的可安装rpm包的所在目录 | %_rpmdir |
SOURCES | 所有源代码和补丁文件的存放目录 | %_sourcedir |
SPECS | 存放SPEC文件的目录(重要) | %_specdir |
SRPMS | 软件最终的rpm源码格式存放路径(暂时忽略掉,别挂在心上) | %_srcrpmdir |
小技巧:执行rpmdev-setuptree会在当前用户家目录下的rpmbuild目录(如果该目录不存在也会被自动创建)里自动建立上述目录。
当上述目录建立好之后,将所有用于生成rpm包的源代码、shell脚本、配置文件都拷贝到SOURCES目录里,注意通常情况下源码的压缩格式都为*.tar.gz格式(当然还可以为其他格式,但那就是另外一种方式,这里先不介绍)。然后,将最最最重要的SPEC文件,命名格式一般是“软件名-版本.spec”的形式,将其拷贝到SPECS目录下,切换到该目录下执行:
*rpmbuild -bb 软件名-版本.spec ***
最终我们想要的rpm软件包就安安稳稳地躺在RPMS目录下了。对,就这么简单。
这里的关键就是上面的SPEC文件的写法,我们可以用rpmdev-newspec -o Name-version.spec命令来生成SPEC文件的模板,然后在上面修改就可。例如:
点击(此处)折叠或打开
[root@localhost ~]# rpmdev-newspec -o myapp-0.1.0.spec
Skeleton specfile (minimal) has been created to “myapp-0.1.0.spec”.
[root@localhost ~]# cat myapp-0.1.0.spec
Name: myapp-0.1.0
Version:
Release: 1%{?dist}
Summary:
Group:
License:
URL:
Source0:
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires:
Requires:
%description
%prep
%setup -q
%build
%configure
make %{?_smp_mflags}
%install
rm -rf $RPM_BUILD_ROOT
make install DESTDIR=$RPM_BUILD_ROOT
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
%doc
%changelog
其实SPEC文件的核心是它定义了一些“阶段”(%prep、%build、%install和%clean),当rpmbuild执行时它首先会去解析SPEC文件,然后依次执行每个“阶段”里的指令。
接下来,我们来简单了解一下SPEC文件的头部。假如,我们的源码包名字是myapp-0.1.0.tar.gz,那么myapp-0.1.0.spec的头部一般如下的样子:
点击(此处)折叠或打开
Name: myapp <===软件包的名字(后面会用到)
Version: 0.1.0 <===软件包的版本(后面会用到)
Release: 1%{?dist} <===发布序号
Summary: my first rpm <===软件包的摘要信息
Group: <===软件包的安装分类,参见/usr/share/doc/rpm-4.x.x/GROUPS这个文件
License: GPL <===软件的授权方式
URL: <===这里本来写源码包的下载路径或者自己的博客地址或者公司网址之类
Source0: %{name}-%{version}.tar.gz <===源代码包的名称(默认时rpmbuid回到SOURCES目录中去找),这里的name和version就是前两行定义的值。如果有其他配置或脚本则依次用Source1、Source2等等往后增加即可。
BuildRoot: %{_topdir}/BUILDROOT <=== 这是make install时使用的“虚拟”根目录,最终制作rpm安装包的文件就来自这里。
BuildRequires: <=== 在本机编译rpm包时需要的辅助工具,以逗号分隔。假如,要求编译myapp时,gcc的版本至少为4.4.2,则可以写成gcc >=4.2.2。还有其他依赖的话则以逗号分别继续写道后面。
Requires: <=== 编译好的rpm软件在其他机器上安装时,需要依赖的其他软件包,也以逗号分隔,有版本需求的可以
%description <=== 软件包的详细说明信息,但最多只能有80个英文字符。
下面我们来看一下制作rpm包的几个关键阶段,以及所发生的事情:
阶段 | 动作 |
---|---|
%prep | 将%_sourcedir目录下的源代码解压到%_builddir目录下。如果有补丁的需要在这个阶段进行打补丁的操作 |
%build | 在%_builddir目录下执行源码包的编译。一般是执行./configure和make指令 |
%install | 将需要打包到rpm软件包里的文件从%_builddir下拷贝%_buildrootdir目录下。当用户最终用rpm -ivh name-version.rpm安装软件包时,这些文件会安装到用户系统中相应的目录里 |
制作rpm包 | 这个阶段是自动完成的,所以在SPEC文件里面是看不到的,这个阶段会将%_buildroot目录的相关文件制作成rpm软件包最终放到%_rpmdir目录里 |
%clean | 编译后的清理工作,这里可以执行make clean以及清空%_buildroot目录等 |
每个阶段的详细说明如下:
1.21.2.1 %prep阶段
这个阶段里通常情况,主要完成对源代码包的解压和打补丁(如果有的话),而解压时最常见到的就是一句指令:
*%setup -q ***
当然,这句指令可以成功执行的前提是你位于SOURCES目录下的源码包必须是name-version.tar.gz的格式才行,它还会完成后续阶段目录的切换和设置。如果在这个阶段你不用这条指令,那么后面每个阶段都要自己手动去改变相应的目录。解压完成之后如果有补丁文件,也在这里做。想了解的童鞋可以自己去查查如何实现,也不难,这里我就不展开了。
1.21.2.2 %build阶段
这个阶段就是执行常见的configure和make操作,如果有些软件需要最先执行bootstrap之类的,可以放在configure之前来做。这个阶段我们最常见只有两条指令:
%configure
make %{?_smp_mflags}
它就自动将软件安装时的路径自动设置成如下约定:
可执行程序/usr/bin
依赖的动态库/usr/lib或者/usr/lib64视操作系统版本而定。
二次开发的头文件/usr/include
1.21.2.3 文档及手册/usr/share/man
注意,这里的%configure是个宏常量,会自动将prefix设置成/usr。另外,这个宏还可以接受额外的参数,如果某些软件有某些高级特性需要开启,可以通过给%configure宏传参数来开启。如果不用 %configure这个宏的话,就需要完全手动指定configure时的配置参数了。同样地,我们也可以给make传递额外的参数,例如:
make %{?_smp_mflags} CFLAGS=”” …
1.21.2.4 %install阶段
这个阶段就是执行make install操作。这个阶段会在%_buildrootdir目录里建好目录结构,然后将需要打包到rpm软件包里的文件从%_builddir里拷贝到%_buildrootdir里对应的目录里。这个阶段最常见的两条指令是:
- rm -rf $RPM_BUILD_ROOT
make install DESTDIR=$RPM_BUILD_ROOT
其中$RPM_BUILD_ROOT也可以换成我们前面定义的BuildRoot变量,不过要写成%{buildroot}才可以,必须全部用小写,不然要报错。
如果软件有配置文件或者额外的启动脚本之类,就要手动用copy命令或者install命令你给将它也拷贝到%{buildroot}相应的目录里。用copy命令时如果目录不存在要手动建立,不然也会报错,所以推荐用install命令。
1.21.2.5 %clean阶段
编译完成后一些清理工作,主要包括对%{buildroot}目录的清空(当然这不是必须的),通常执行诸如make clean之类的命令。
1.21.2.6 制作rpm软件包的阶段
这个阶段必须引出下面一个叫做%files的阶段。它主要用来说明会将%{buildroot}目录下的哪些文件和目录最终打包到rpm包里。
%files
%defattr(-,root,root,-)
%doc
在%files阶段的第一条命令的语法是:
*%defattr(文件权限,用户名,组名,目录权限) ***
如果不牵扯到文件、目录权限的改变则一般用%defattr(-,root,root,-)这条指令来为其设置缺省权限。所有需要打包到rpm包的文件和目录都在这个地方列出,例如:
%files
%{_bindir}/*
%{_libdir}/*
%config(noreplace) %{_sysconfdir}/*.conf
在安装rpm时,会将可执行的二进制文件放在/usr/bin目录下,动态库放在/usr/lib或者/usr/lib64目录下,配置文件放在/etc目录下,并且多次安装时新的配置文件不会覆盖以前已经存在的同名配置文件。
这里在写要打包的文件列表时,既可以以宏常量开头,也可以为“/”开头,没任何本质的区别,都表示从%{buildroot}中拷贝文件到最终的rpm包里;如果是相对路径,则表示要拷贝的文件位于%{_builddir}目录,这主要适用于那些在%install阶段没有被拷贝到%{buildroot}目录里的文件,最常见的就是诸如README、LICENSE之类的文件。如果不想将%{buildroot}里的某些文件或目录打包到rpm里,则用:
*%exclude dic_name或者file_name ***
但是关于%files阶段有两个特性要牢记:
%{buildroot}里的所有文件都要明确被指定是否要被打包到rpm里。什么意思呢?假如,%{buildroot}目录下有4个目录a、b、c和d,在%files里仅指定a和b要打包到rpm里,如果不把c和d用exclude声明是要报错的;
如果声明了%{buildroot}里不存在的文件或者目录也会报错。
关于%doc宏,所有跟在这个宏后面的文件都来自%{_builddir}目录,当用户安装rpm时,由这个宏所指定的文件都会安装到/usr/share/doc/name-version/目录里。
1.21.2.7 %changelog阶段
这是最后一个阶段,主要记录的每次打包时的修改变更日志。标准格式是:
** date +”%a %b %d %Y” 修改人 邮箱 本次版本x.y.z-p ***
*- 本次变更修改了那些内容 ***
说了这么多,我们实战一下。网上很多教程都是拿Tomcat或者Nigix开头,这里我就先从简单的mp3解码库libmad入手,将它打成一个rpm包,具体步骤如下:
(如果自己系统上没有rpmbuild命令就安装之:yum install rpm* rpm-build rpmdev*)
1、构建rpm的编译目录结构:
2、下载libmad源码到rpmbuild/SOURCES目录下,可以从 http://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz这里下载。
3、在rpmbuild/SPECS目录下执行rpmdev-newspec -o libmad-0.15.1b.spec,会在当前目录下生成名为libmad-0.15.1b.spec的模板文件。
4、将libmad-0.15.1b.spec修改成如下的样子:
5、在rpmbuild/SPECS目录下执行打包编译:
*rpmbuild -bb libmad-0.15.1b.spec ***
最终生成的rpm包如下:
因为我是64位系统,所以编译出的libmad适用于CentOS6.0-64。
如果我们将libmad的源码和spec文件拷贝32位系统上,再执行rpm打包,看看结果:
结果如下:
后记:
关于SPEC文件,还有一些其他特性,诸如安装软件包之前、之后要做的事情,以及卸载软件包之前之后要做的事情,包括给源码打补丁,关于这些特性感兴趣的童鞋自己去摸索吧。最后给出一个完整的,包含了打补丁、安装、卸载特性的SPEC文件模板:
点击(此处)折叠或打开
Name: test
Version:
Requires:
%description
…
#==================================SPEC头部====================================
%prep
%setup -q
%patch <==== 在这里打包
%build
%configure
make %{?_smp_mflags}
%install
rm -rf $RPM_BUILD_ROOT
make install DESTDIR=$RPM_BUILD_ROOT
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
要打包到rpm包里的文件清单
%doc
%changelog
#==================================SPEC主体====================================
%pre
安装或者升级软件前要做的事情,比如停止服务、备份相关文件等都在这里做。
%post
安装或者升级完成后要做的事情,比如执行ldconfig重构动态库缓存、启动服务等。
%preun
卸载软件前要做的事情,比如停止相关服务、关闭进程等。
%postun
卸载软件之后要做的事情,比如删除备份、配置文件等。
1.22 ping包问题
ping指定的主机,当回响信息分别为目的地不可达、超时、未知的主机名时,分析各自可能的故障原因
一、 目的地不可达,可能出现原因:
1.对方服务器与外网断开连接;
2.对方服务器拒绝Ping入;
3.对方服务器拒绝Ping出;
4.自己计算机与外网已经断开连接。
二、超时,可能出现原因:
1、网卡安装或配置有问题。将网线断开再次执行此命令,如果显示正常,则说明本机使用的IP地址可能与另一台正在使用的机器IP地址重复了。如果仍然不正常,则表明本机网卡安装或配置有问题,需继续检查相关网络配置。
2、对方拒绝接收你发给它的数据包造成数据包丢失。大多数的原因可能是对方装有防火墙或已下线。
三、未知的主机名,可能出现原因:
1、对方主机不存在或者没有跟对方建立连接。
2、主机文件存在问题。
1.23 linux ^M格式问题
在linux下,不可避免的会用VIM打开一些windows下编辑过的文本文件。我们会发现文件的每行结尾都会有一个^M符号,这是因为 DOS下的编辑器和Linux编辑器对文件行末的回车符处理不一致,
对于回车符的定义:
windows:0D0A
unix\linux: 0A
MAC: 0D
比较快捷的去除这些符号的方法有这么几种:
(1)是用VI的命令:
使用vi打开文本文件
vi dos.txt
命令模式下输入
:set fileformat=unix
:w
(2) VI下使用正则表达式替换
g/^M/s/^M//
或者
%s/^M//g
(3)使用sed 工具
sed ’s/^M//’ filename > tmp_filename
(4)既然window下的回车符多了‘\r’,那么当然通过删除‘\r’ ,也可以实现:
tr -d ‘\r’
(5)最后一个方法是本人最常用的方法,个人觉得最方便
在终端下敲命令:
$ dos2unix filename
直接转换成unix格式,就OK了!~
1.24 linux下解决端口被占用问题
查找被占用的端口:
netstat -tln
netstat -tln | grep 8080
查看端口属于哪个程序
lsof -i :8080
杀掉占用端口的进程:
kill -9 进程ID
ps -ef | grep xxx
kill xxx
1.25 切换系统中英文
修改/etc/sysconfig/i18n
LANG=”zh_CN.UTF-8”
LANG=”en_US.UTF-8”
第 2 章 Linux软件
2.1 boa配置
2.1.1 boa.conf
/etc/boa/下的boa.conf如下:
Port 80**
User root**
Group root**
ErrorLog /dev/console**
AccessLog /dev/null**
ServerName friendly-arm**
DocumentRoot /dau/boa**
#DocumentRoot /www**
DirectoryIndex index.html**
KeepAliveMax 1000**
KeepAliveTimeout 10**
MimeTypes /etc/mime.types**
DefaultType text/plain**
CGIPath /bin:/daucgipath**
AddType application/x-httpd-cgi cgi**
2.2 Vi编辑器
Vim vi improved
i 在光标前插入
a 在光标后插入
2.2.1 进入VI
vi a.x
2.2.2 退出Vi
:q! 强制退出
:wq 保存退出
w filename 另存为文件
^ 光标跳转至行首
$ 光标跳转至行尾
:set nu 显示行号
nG 跳转至文件中的第n行
dd 删除光标所在行
ndd 删除光标所在行向下删除n行
u 取消最后一次操作
yy 复制当前行
nyy /yny 复制n行
p 读取vi缓冲区的内容,并粘贴到光标当前位置
/word 从上而下查找“word”
?word 从下而上查找“word”
n 查找下一个匹配的被查找字符串
2.2.3 调用shell命令
e.g. :r!ls /root 把文件列表插入当前编辑文本中
2.2.4 编译过程四个阶段
l 预处理(Pre-Processing)
在该阶段,编译器将C源代码中的包含的头文件如stdio.h编译进来,用户可以使用gcc的选项”-E”进行查看。
用法:#gcc -E hello.c -o hello.i
作用:将hello.c预处理输出hello.i文件。
l 编译(Compiling)
第二步进行的是编译阶段,在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
选项 -S
用法:[root]# gcc –S hello.i –o hello.s
作用:将预处理输出文件hello.i汇编成hello.s文件。
l 汇编(Assembling)
汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码.
选项 -c
用法:[root]# gcc –c hello.s –o hello.o
作用:将汇编输出文件test.s编译输出test.o文件。
l 链接(Linking)
在成功编译之后,就进入了链接阶段。
无选项链接
用法:[root]# gcc hello.o –o hello.exe
作用:将编译输出文件hello.o链接成最终可执行文件hello.exe。
2.2.5 gcc常用编译选项
-c 只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件。
-o output_filename:确定可执行文件的名称为output_filename。如果不给出这个选项,gcc就给出预设的可执行文件a.out。(演示)
-g 产生调试工具(GNU的gdb)所必要的符号信息,要想对编译出的程序进行调试,就必须加入这个选项。
-O 对程序进行优化编译、链接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。
-O2 比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
-Wall 生成所有警告信息
gcc –O optimize.c -o optimizetime ./optimize
第 3 章 Linux基础
Linux严格说只是一个操作系统内核,实现了操作系统的核心功能:内存管理、进程管理、文件系统管理、和设备管理等。
3.1 Linux标准目录
· root — 启动Linux时使用的一些核心文件。如操作系统内核、引导程序Grub等。
· home — 存储普通用户的个人文件
· ftp — 用户所有服务
· httpd
· samba
· user1
· user2
· bin — 系统启动时需要的执行文件(二进制)
· sbin — 可执行程序的目录,但大多存放涉及系统管理的命令。只有root权限才能执行
· proc — 虚拟,存在linux内核镜像;保存所有内核参数以及系统配置信息
· usr — 用户目录,存放用户级的文件
· bin — 几乎所有用户所用命令,另外存在与/bin,/usr/local/bin
· sbin — 系统管理员命令,与用户相关,例如,大部分服务器程序
· include — 存放C/C++头文件的目录
· lib — 固定的程序数据
· local — 本地安装软件保存位置
· man — 手工生成的目录
· info — 信息文档
· doc — 不同包文档信息
· tmp
· X11R6 — 该目录用于保存运行X-Window所需的所有文件。该目录中还包含用于运行GUI要的配置文件和二进制文件。
· X386 — 功能同X11R6,X11 发行版5 的系统文件
· boot — 引导加载器所需文件,系统所需图片保存于此
· lib — 根文件系统目录下程序和核心模块的公共库
· modules — 可加载模块,系统崩溃后重启所需模块
· dev — 设备文件目录
· etc — 配置文件
· skel — home目录建立,该目录初始化
· sysconfig — 网络,时间,键盘等配置目录
· var
· file
· lib — 该目录下的文件在系统运行时,会改变
· local — 安装在/usr/local的程序数据,变化的
· lock — 文件使用特定外设或文件,为其上锁,其他文件暂时不能访问
· log — 记录日志
· run — 系统运行合法信息
· spool — 打印机、邮件、代理服务器等假脱机目录
· tmp
· catman — 缓存目录
· mnt — 临时用于挂载文件系统的地方。一般情况下这个目录是空的,而在我们将要挂载分区时在这个目录下建立目录,再将我们将要访问的设备挂载在这个目录上,这样我们就可访问文件了。
· tmp — 临时文件目录,系统启动后的临时文件存放在/var/tmp
· lost+found — 在文件系统修复时恢复的文件
/:根目录,一般根目录下只存放目录,不要存放文件,/etc、/bin、/dev、/lib、/sbin应该和根目录放置在一个分区中
**/bin:/usr/bin:**可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。
/boot:放置linux系统启动时用到的一些文件。/boot/vmlinuz为linux的内核文件,以及/boot/gurb。建议单独分区,分区大小100M即可
/dev:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,常用的是挂载光驱mount /dev/cdrom /mnt。
/etc:系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有/etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d修改配置文件之前记得备份。注:/etc/X11存放与x windows有关的设置。
/home:系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,
表示当前用户的家目录,test表示用户test的家目录。建议单独分区,并设置较大的磁盘空间,方便用户存放数据
/lib:/usr/lib:/usr/local/lib:系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助,比较重要的目录为/lib/modules。
/lost+fount:系统异常产生错误时,会将一些遗失的片段放置于此目录下,通常这个目录会自动出现在装置目录下。如加载硬盘于/disk 中,此目录下就会自动产生目录/disk/lost+found
/mnt:/media:光盘默认挂载点,通常光盘挂载于/mnt/cdrom下,也不一定,可以选择任意位置进行挂载。
/opt:给主机额外安装软件所摆放的目录。如:FC4使用的Fedora 社群开发软件,如果想要自行安装新的KDE 桌面软件,可以将该软件安装在该目录下。以前的 Linux 系统中,习惯放置在 /usr/local 目录下
/proc:此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有/proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/*等
/root:系统管理员root的家目录,系统第一个启动的分区为/,所以最好将/root和/放置在一个分区下。
/sbin:/usr/sbin:/usr/local/sbin:放置系统管理员使用的可执行命令,如fdisk、shutdown、mount等。与/bin不同的是,这几个目录是给系统管理员root使用的命令,一般用户只能”查看”而不能设置和使用。
/tmp:一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下
/srv:服务启动之后需要访问的数据目录,如www服务需要访问的网页数据存放在/srv/www内
/usr:应用程序存放目录,**/usr/bin存放应用程序,/usr/share存放共享数据,/usr/lib存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local:存放软件升级包。/usr/share/doc:系统说明文件存放目录。/usr/share/man:** 程序说明文件存放目录,使用 man ls时会查询/usr/share/man/man1/ls.1.gz的内容建议单独分区,设置较大的磁盘空间
/var:放置系统执行过程中经常变化的文件,如随时更改的日志文件/var/log,**/var/log/message:所有的登录文件存放目录,/var/spool/mail:邮件存放的目录,/var/run:程序或服务启动后,其PID存放在该目录下。建议单独分区,设置较大的磁盘空间**
3.2 Linux名词
3.2.1 NAT
network address translation 网络地址转换
3.2.2 命名
linux 2.4.18 主版本号2 从版本号4 补丁18
3.3 快捷键
ctrl+Alt+F1 进入图形界面
ctrl+alt+F2/F3 进入命令界面
+F7 返回
ctrl+D 结束
which+x 查询x是否存在
TAB 自动补全
Ctrl+Alt+à/ß 切换虚拟机、切换workspace
Shift+ctrl+N 新建文件夹
3.3.1 在终端界面下
Shift+ctrl+N 新建窗口
Shift+ctrl+T 新建标签
Alt+1~9 切换标签页
复制:Shift+Ctrl+C
粘贴:Shift+Ctrl+V
开启新窗口:Shift+Ctrl+N
开启新分页(Tab):Shift+Ctrl+T
3.3.2 设置快捷键
System->Preferences->Keyboard Shortcuts
在Desktop分类下找到“Run a terminal”
点击Run a terminal,按下需要的快捷键,比如Ctrl+Alt+T,即可。
3.4 安装软件
A .configure
B make
C make install
D source /etc/profile 保持环境变量生效
3.5 文件操作
3.5.1 -rwx rwx rwx 文件拥有者 文件所有组 其他用户
- 普通文件
r 可读
w 可写
x 可执行
3.5.2 磁盘分区的文件名表示
/dev/hda5
dev:所有设备文件的目录
hd:硬件设备代号 hd:IDE设备(Integrated-Drive-Electronics是原来普遍使用的外部接口,主要接硬盘和光驱);sd:SCSI,SATA设备
a是指同类型设备编号 a:第一个硬盘,b:第二个硬盘
5是指分区号
3.5.3 隐藏文件
在文件前加.
3.6 minicom
Linux下串口通信软件
3.7 开启root账户方法
A 命令
sudo passwd root
禁用 sudo passwd -l root
B 在终端模式下切换root
sudo -s -H
3.8 fedora以root登录的方法
fedora从10开始就禁止了以root来登录系统,这个可能是出于安全的考虑,毕竟root的权限太大了,出余增强系统安全性,而且普通的账号可以运行一般应该用户的应用程序来说足够了,如果需要用到root权限时,会自动提示输入密码,可以说在安全考虑上做到相当完美了.
步骤/方法
想用root登录系统该怎么办呢?在网上找到了资料解决如下:
修改目录 /etc/pam.d/gdm与/etc/pam.d/gdm-password两个文件
一般修改方法:
在终端中输入su命令并输入root密码这样我在终端中就有了root的操权限接下来我们使用gdit命令对其进行修改
gdit /etc/pam.d/gdm
在这段”auth required pam_succeed_if.so user != root quiet”加上#号注释掉就可以了
同样的gdit /etc/pam.d/gdm-password
也是在auth required pam_succeed_if.so user != root quiet前面加上#号注释
保存之后我们重启或者注销一次计算机就能以root进行登录系统了
当然如果会使用vi命令的话这个最方便
3.9 重命名逻辑卷名
vgrename 原名 新名
提示:最好不要使用,多次使用证明,这样会导致虚拟机无法打开****
3.10 busybox
BusyBox 是一个集成了一百多个最常用linux命令和工具的软件。BusyBox 包含了一些简单的工具,例如ls、cat和echo等等,还包含了一些更大、更复杂的工具,例grep、find、mount以及telnet。有些人将 BusyBox 称为 Linux 工具里的瑞士军刀。简单的说BusyBox就好像是个大工具箱,它集成压缩了 Linux 的许多工具和命令,也包含了 Android 系统的自带的shell。
3.11 linux移植
修改rcS文件下的内容改变root@FrindlyARM
3.11.1 Linux内核
Linux由内核空间和用户空间两部分组成。
3.11.1.1 arch目录
architecture的缩写,内核支持的每种CPU体系
3.11.1.2 block
部分块设备的驱动程序
3.11.1.3 drives
设备驱动程序
3.11.1.4 fs
文件系统
3.11.1.5 include
内核所需的头文件
3.11.1.6 lib
库文件代码
3.11.1.7 mm
用于实现内存管理与体系结构无关代码
3.11.1.8 net
网络协议代码
3.11.1.9 usr
cpio命令实现
移植需要修改的两个目录:arch、driver
3.11.2 内核配置与编译
内核配置 a.修改makefile里的ARCH := arm b. make menuconfig
建立依赖关系 make
创建内核镜像 make zImage
[1] 清除临时文件、中间文件、配置文件
make clean
删除产生文件保留配置文件
make mrproper
删除所有产生文件+config files
make distclean
删除 mrproper+remove editor backup and patch files
[2] 确定目标系统的软硬件配置情况,比如cpu类型、网卡型号、网络协议等。
[3] 使用如下命令配置内核
make config 基于文本模式的交互式配置
make menuconfig 基于文本模式的菜单型配置
make oldconfig
make xconfig 图形化界面配置
配置时, * Y
[ ] N 不编译
M module 只编译不连接
[4] 编译内核
make zImage 小于512k的内核(在x86)
make bzImage
如需获取详细信息
make zImage v=1
make bzImage v=1
编译好的内核位于 arch /
[5] 编译内核步骤
以root用户登录
拷贝config_mini6410_h43为文件.config
执行make menuconfig 定制内核组件
编译内核 make zImage
编译完成后内核镜像文件生成在/arch/arm/boot/..
[6] 编译内核模块
make modules
[7] 安装内核模块
make modules_install
将编译好的内核模块从内核代码目录copy至/lib/modules下
[8] 制作init ramdisk
mkinitrd initrd -$version $version
例如,mkinitrd initrd-2.6.29 2.6.29
$version可以通过查询/lib/modules下的目录得到
3.11.3 内核安装
[1] cp arch/x86/boot/bzImage /boot/vmlinuz -$version
[2] cp $initrd /boot/
[3] 修改 /etc/grub.conf 或者/etc/lilo.conf
3.11.4 内核模块
3.11.4.1 加载函数 module_init(hello_init)
3.11.4.2 卸载函数 module_exit(hello_exit)
3.11.4.3 模块编译 makefile
obj -m :=hello.o
hello-objs:= main.o add.o
obj-m:=a.o
a-objs:=main.o add.o
3.11.4.4 模块安装与卸载
加载 insmod insmod hello.ko
卸载 rmmod rmmod hello
查看 lsmod
加载 modprobe modprobe hello
3.11.4.5 MODULE_LICENSE
用来告知内核该模块带有一个许可证
module_author(“…”)
module_alias(“a simple”)
3.11.4.6 模块参数
通过宏module_param指定模块参数
module_param(name,type,perm)
name:模块参数名称
type:参数类型 bool,charp(字符串型)
perm:参数范围权限
条用几个模块时,参数要使用导出export_symbol <参数>
内核符号导出:
EXPORT_SYMBOL
EXPORT_SYMBOL_GPL
.ko .o+内核相关文件
printk在内核中使用
printf在应用程序中使用
3.11.4.7 模块中的问题
[1] rmmod: chdir(/lib/modules): No such file or directory 解决方法
3.11.5 搭建NFS根文件系统
[1] 解压根文件系统源码包
[2] 配置NFS服务
i. 设置NFS共享目录
ii. 启动NFS服务
iii. 设置u-boot引导参数,启动内核挂载网络根文件系统
3.11.6 基本脚本编程
[1] 使用Vi建立shell脚本文件
vi hello.sh
[2] 脚本中应包括的内容:
Ø 脚本运行环境,指明使用的哪种shell来解释脚本
#!/bin/bash
Ø 注释行以#开始
#
Ø 脚本语句
shell命令+系统命令
[3] 设置脚本文件为可执行属性(+x)
chmod u+x hello.sh
u+x 创建者具有可执行权限
chmod +x hello.sh 所有人都具有可执行权限
例:#!/bin/bash
#hello
cd
mkdir test
cd test
tauch hello
echo”hello world”>hello
cat hello
3.11.7 printk
3.12 应用程序的开发
ARM环境下应用程序的开发——–编写helloworld程序
[1] 使用vi编写helloworld程序
[2] 使用arm-linux-gcc编译helloworld程序
[3] 通过串口下载可执行文件至开发板中并运行(rz指令)
注意:运行前,先设置为可执行权限,chmod +x hello
3.12.1 概念
3.12.1.1 API
用户编程接口。
3.12.2 helloworld
3.12.3 irqreturn_t
关于中断处理函数的返回值:中断程序的返回值是一个特殊类型—irqreturn_t。但是中断程序的返回值却只有两个—IRQ_NONE和IRQ_HANDLED。
#ifndef _LINUX_IRQRETURN_H
#define _LINUX_IRQRETURN_H
typedef int irqreturn_t;
#define IRQ_NONE (0)
#define IRQ_HANDLED (1)
#define IRQ_RETVAL(x) ((x) != 0) //这个宏只是返回0或非0
3.12.4 system函数
system(“kill -s STOP pidof led-player
“);
pidof 命令,捕获led-player的进程号
3.12.5 size_t time_t
size_t是标准C库中定义的,应为unsigned int,在64位系统中为 long unsigned int。
time_t的单位是秒, time_t实际上是长整型. typedef long time_t
3.13 Linux驱动程序
linux的外设可分为3类:字符设备(character device)、块设备(block device)和网络接口(network interface)。
字符设备是指能够像字节流一样被访问的设备,就是说对它的读写是以字节为单位的。
linux驱动在开发板运行时表现为一个设备即dev,所以通常使用open函数调用/dev/leds
3.13.1 编写Linux驱动
[1] 编写helloworld驱动程序
[2] 为驱动程序编写makefile
[3] 执行make,生成ARM版本的.ko模块文件
[4] 上电开发板,启动内核
[5] 通过串口下载.ko文件至开发板
[6] 在当前开发板运行的嵌入式linux内核中动态加载驱动模块文件
insmod hello.ko
rmmod hello
lsmod 查看
3.13.2 加载内核中已有模块
[1] make menuconfig 重新编译内核,去掉即将要加载的内核选项
[2] 烧写内核
[3] 加载模块驱动
3.13.3 函数
3.13.3.1 copy_to_user
作用:从内核区中读取数据到用户区
简述:
#include <linux/uaccess.h>
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
*to是用户空间的指针,
*from是内核空间指针,
n表示从内核空间向用户空间拷贝数据的字节数
一、Copy_to_user( to, &from, sizeof(from))
To:用户空间函数 (可以是数组)
From:内核空间函数(可以是数组)
sizeof(from):内核空间要传递的数组的长度
二、Copy_from_user(&from , to , sizeof(to) )
To:用户空间函数 (可以是数组)
From:内核空间函数(可以是数组)
sizeof(from):内核空间要传递的数组的长度
成功返回0,
失败返回失败数目。
例子:
static ssize_t keys4_read(struct inode *inode, char *buf, unsigned long count,loff_t *ppos)
{
int co;
char buff[2];
buff[0] = times3;
buff[1] = times4;
if((co=copy_to_user(buf ,&buff , sizeof(buff))))
return -EFAULT;
else printk(“copy to user is okn”);
Return 1;
}
3.13.3.2 File_operations
3.14 Linux生成动态库
附件:附件\文档\Linux下Gcc生成和使用静态库和动态库详解.pdf
3.14.1 生成动态链接库
gcc -shared -fpic -o libmyhello.so hello.c
-fpic 使输出的对象模块是按照可重定位地址方式生成的。
-shared指定把对应的源文件生成对应的动态链接库文件。
静态库链接时搜索路径顺序:
\1. ld 会去找 GCC 命令中的参数-L
\2. 再找 gcc 的环境变量 LIBRARY_PATH
\3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初 compile gcc 时写在程序内的
动态链接时、执行时搜索路径顺序:
\1. 编译目标代码时指定的动态库搜索路径;
\2. 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径;
\3. 配置文件/etc/ld.so.conf 中指定的动态库搜索路径;
\4. 默认的动态库搜索路径/lib;
\5. 默认的动态库搜索路径/usr/lib。
有关环境变量:
LIBRARY_PATH 环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH 环境变量:指定程序动态链接库文件搜索路径
3.14.2 linux 增加动态库的搜索路径
在/etc/ld.so.conf中加一行/usr/local/lib,这是你要增加的动态库目录,然后运行ldconfig。
在/etc/ld.so.conf.d/下加入一个文件,文件内容为so路径名
3.15 静态库的生成
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
ar -cr libmyhello.a hello.o
gcc -o t main.c libmyhello.a
ar rv libso.a hello.o
3.15.1 几个Linux文件的作用
(1) /etc/profile: 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. 并从/etc/profile.d目录的配置文件中搜集shell的设置。
(2) /etc/bashrc: 为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取(即每次新开一个终端,都会执行bashrc)。
(3) ~/.bash_profile: 每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次。默认情况下,设置一些环境变量,执行用户的.bashrc文件。
(4) ~/.bashrc: 该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该该文件被读取。
(5) /.bash_logout: 当每次退出系统(退出bash shell)时,执行该文件. 另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而/.bashrc等中设定的变量(局部)只能继承 /etc/profile中的变量,他们是”父子”关系。
(6) /.bash_profile: 是交互式、login 方式进入 bash 运行的/.bashrc 是交互式 non-login 方式进入 bash 运行的通常二者设置大致相同,所以通常前者会调用后者。
3.16 启动脚本
系统启动后会执行脚本/etc/rc.local它在服务/etc/init.d/rc.local中被调用,可以在这里进行一些系统级的初始化工作。
3.16.1 bash启动脚本
bash启动也会自动执行一些脚本
/etc/profile 全局登陆脚本,任何用户登陆都执行
/etc/bash.bashrc 全局bash脚本,任何用户启动非登陆的交互式bash时执行
~/.profile 用户登陆脚本,用户登陆时执行
~/.bashrc 用户bash脚本,用户启动费登陆的交互式bash执行
3.17 DHCP
/etc/dhcpd.conf
3.18 Linux下清理内存和Cache方法
/proc/sys/vm/drop_caches
频繁的文件访问会导致系统的Cache使用量大增
$ free -m
total used free shared buffers cached
Mem: 3955 3926 28 0 55 3459
-/+ buffers/cache: 411 3544
Swap: 5726 0 5726
free内存减少到几十兆,系统运行缓慢
运行sync将dirty的内容写回硬盘
$sync
通过修改proc系统的drop_caches清理free的cache
$echo 3 > /proc/sys/vm/drop_caches
drop_caches的详细文档如下:
Writing to this will cause the kernel to drop clean caches, dentries and inodes from memory, causing that memory to become free.
To free pagecache:
* echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
* echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
* echo 3 > /proc/sys/vm/drop_caches
As this is a non-destructive operation, and dirty objects are notfreeable, the user should run “sync” first in order to make sure allcached objects are freed.
This tunable was added in 2.6.16.
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2010-03/24939.htm
第 4 章 Shell
4.1 shell语法
4.1.1 参数
#!/bin/sh
#!用来指定
注释以“#”开头,所有变量没有类型也不用声明
$#:传入脚本的命令行参数个数,保存程序命令行参数的数目
$*:所有命令行参数值,在各个参数值之间留有空格
$0:命令本身(shell文件名)
$1:第一个命令行参数
$2:第二个命令行参数
注意: 变量赋值时,“=”左右两边都不能有空格, BASH 中的语句结尾不需要分号
$$:Shell本身的PID(ProcessID)
$!:Shell最后运行的后台Process的PID
$?:最后运行的命令的结束代码(返回值)
$-:使用Set命令设定的Flag一览
$:所有参数列表。如”$“用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。
$@:所有参数列表。如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。
$#:添加到Shell的参数个数
$0:Shell本身的文件名
$1~$n:添加到Shell的各参数值。$1是第1参数、$2是第2参数…。
e.g.
我们先写一个简单的脚本,执行以后再解释各个变量的意义
# touch variable
# vi variable
脚本内容如下:
#!/bin/sh
echo “number:$#”
echo “scname:$0”
echo “first :$1”
echo “second:$2”
echo “argume:$@”
保存退出
赋予脚本执行权限
# chmod +x variable
执行脚本
# ./variable aa bb
number:2
scname:./variable
first: aa
second:bb
argume:aa bb
通过显示结果可以看到:
$# 是传给脚本的参数个数
$0 是脚本本身的名字
$1是传递给该shell脚本的第一个参数
$2是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
4.1.2 内部命令
exit 终止当前shell的执行
export 设置一个环境变量,当前shell的所有子进程都可以访问这个环境变量
kill 终止某个进程的执行。
4.1.3 bash功能
A. 命令行编辑
B. 命令和文件不全功能(TAB)
C. 命令历史功能(history)
D. 命令别名功能(alias)
E. 作业控制
F. 自定义功能键
G. 灵活的shell脚本编程
注意:shell变量符号”=”变量赋值时两边不能有空格,当变量有空格时要用“
如:DAY=Monday DAY=”Today is Monday”
引用:echo $DAY echo “$DAY”
4.1.4 添加自定义命令
文件:/root/.bashrc,添加以下行:alias mycodedir = “cd /root/Source_code”,保存,然后重新启动,以后在命令行输入mycodedir 即可执行命令“cd /root/Source_code”,省事又省力
echo $PATH
看一下自己的命令会去哪里搜索,然后把自定义命令放到其中一个文件夹里面就ok了。
4.1.5 反引号
echo date
执行引号中的系统命令
4.1.6 测试语句
4.1.7 if语句
if [ expression ]
then
#code block
fi
if [ expression ]
then
#code block
else
#code block
fi
if [ expression]
then
#code block
elseif [ expression]
then
#code block
else
#code block
fi
fi
4.1.8 比较
比较操作 整数操作 字符串操作
相同 -eq =
不同 -ne !=
大于 -gt > greater than
小于 -lt < less than
大于或等于 -ge
小于或等于 -le
为空 -z
不为空 -n
例:
比较整数a和b是否相等:if [ $a = $b ] (也可用eq)
判断整数a是否大于整数b:if [ $a-gt $b]
比较字符串a和b是否相等:if [ $a = $b]
判断字符串a是否为空: if [-z $a]
判断整数变量a是否大于b:if [ $a-gt $b]
注意:
1. 在“[”和“]”符号的左右都留有空格****
2. “=”左右都有空格****
000011.1 判断
-e 文件已经存在
-f 文件是普通文件
-s 文件大小不为零
-d 文件是一个目录
-r 文件对当前用户可以读取
-w 文件对当前用户可以写入
-x 文件对当前用户可以执行
例S5:
#!/bin/sh
folder=/home
[ -r “$folder” ] && echo “Can read $folder”
[ -f “$folder” ] || echo “thisis not file”
4.1.9 case语句
#!/bin/bash
echo “Hit a key, then hit return.”
read Keypress
case “$Keypress”in
[A-Z] ) echo “ Uppercaseletter”;;
[a-z] ) echo “ Lowercaseletter”;;
[0-9] ) echo “Digit”;;
* ) echo “Punctuation, whitespace, or other”;;
esac
4.1.10 until循环
until 循环的基本结构是:
until [condition]
do
#code block
done
4.1.11 while循环
while 循环的基本结构是:
while [ condition ]
do
#code block
done
4.1.12 For循环
#!/bin/bash
for day in Sun Mon Tue Wed Thu Fri Sat
do
echo $day
done
如果列表被包含在一对双引中,则被认为是一个元素,如:
#!/bin/bash
for day in “Sun Mon Tue Wed Thu Fri Sat“
do
echo $day
done
4.1.13 标准输入输出
输入输出文件 文件描述符 默认设备
标准输入 0 键盘
标准输出 1 终端屏幕
标准错误输出 2 终端屏幕
输入重定向 < 以只读方式打开文件,将命令中接收接入的途径由默认的键盘更改为指定的文件
输出重定向 > 以只写方式打开文件,文件原内容清空
输出重定向 >> 追加到文件的末尾保存
错误重定向 2>
wc < /bin/passwd
统计passwd的行数
ls /etc/ > sh.bash 会覆盖
将标准输出和错误输出重定向到文件
ls afile bfile &>errfile
管道 “|” 左边的输出结果作为右边的输入
4.2 Shell
4.2.1 获取系统时间作为文件名
示例:
#!/bin/bash
cd /workspace/projects/im/lte/sgw/build/
FILE_NAME=$(date +’%Y%m%d%H%M%S’)
#FILE_NAME=$(date +’%T’)
FILE_PATH=../PfMod/compile
make cleanpf
make pf|tee ${FILE_PATH}/${FILE_NAME}.log
4.2.2 判断某个目录是否存在
4.2.3 延时
sleep 5s
4.2.4 获取程序id然后kill
a=$(ps -a|awk ‘/tcpdump/{print $1}’)
echo $a
kill $a
4.2.5 执行shell时,显示源代码
在 #!/bin/bash 后加入 +x
4.2.6 shell脚本中引入其他文件定义采用source
source /test/hello.sh
4.2.7 一行执行多条命令
用;隔开即可
4.2.8 从文件中得到/分离出IP地址
ParamName=EMS_IP
EMS_IP=cat ${Path}/${ConfigFile} | grep ${ParamName} | head -n 1| cut -d "=" -f 2
4.2.9 替换字符串中的点
A=1.1.1.1
echo $A|sed /./,/
4.3 Shell加密
可以采用gzexe和shc两命令
Gzexe加密同时压缩脚本 gzexe test.sh
Shc加密更为复杂.
Shc –e 2015/2/2 –m “please your lisence” –f test.sh
4.3.1
采用shc工具加密Shell脚本,以提高shell脚本文件的安全性。
介绍:当我们写的shell脚本,存在有敏感信息如账号密码,于是想加强脚本的安全性;还有不想让别人查看/修改您的shell核心代码等等情况。都可使用以下工具进行加密。
shc是一个脚本编译工具, 使用RC4加密算法, 它能够把shell程序转换成二进制可执行文件(支持静态链接和动态链接)。
shc官网:http://www.datsi.fi.upm.es/%7Efrosal/
安装shc
wget http://www.datsi.fi.upm.es/%7Efrosal/sources/shc-3.8.7.tgz
tar vxf shc-3.8.7.tgz
cd shc-3.8.7
make test
make strings
make install
若报错:
*** Installing shc and shc.1 on /usr/local
*** ?Do you want to continue? y
install -c -s shc /usr/local/bin/
install -c -m 644 shc.1 /usr/local/man/man1/
install: target `/usr/local/man/man1/‘ is not a directory: No such file or directory
make: *** [install] Error 1
请创建 mkdir -p /usr/local/man/man1/ ,然后运行make install
常用参数:
-e date (指定过期日期)
-m message (指定过期提示的信息)
-f script_name(指定要编译的shell的路径及文件名)
-r Relax security. (可以相同操作系统的不同系统中执行)
-v Verbose compilation(编译的详细情况)
使用方法:
shc -v -f abc.sh
-v 是现实加密过程
-f 后面跟需要加密的文件
运行后会生成两个文件:
abc.sh.x 和 abc.sh.x.c
abc.sh.x为二进制文件,赋予执行权限后,可直接执行。更改名字mv abc.sh.x a.sh
abc.sh.x.c 是c源文件。基本没用,可以删除
另shc还提供了一种设定有效执行期限的方法,过期时间,如:
# shc -e 28/01/2012 -m “过期了” -f abc.sh
选项“-e”指定过期时间,格式为“日/月/年”;选项“-m”指定过期后执行此shell程序的提示信息。
如果在过期后执行,则会有如下提示:
# ./abc.sh.x
./abc.sh.x: has expired!
过期了
使用以上方法要注意,需防止用户更改系统时间,可以通过在程序中加入自动更新系统时间的命令来解决此问题。
4.4 shell交互
4.4.1 自动交互方法一
自动交互最关键的就是交互信息的自动输入,首先联想到文件重定向,在shell编程中有这样一种用法(参考Linux与UNIX SHELL编程指南 chapt 5.7):”command << delimiter 从标准输入中读入,直至遇到delimiter分界符。”
重定向操作符command << delimiter是一种非常有用的命令,shell将分界符delimiter之后直至下一个同样的分界符之前的所有内容都作为输入,遇到下一个分界符, shell就知道输入结束了。最常见的delimiter分界符是EOF,当然完全可以自定为其他字符。
对于需求1 要求的自动登陆ftp,并作系列操作,则可以用这种方法进行自动交互。代码如下:
1.#!/bin/bash
2.ftp -i -n 192.168.167.187 << EOF
3.user hzc 123456
4.pwd
5.cd test
6.pwd
7.close
8.bye
9.EOF
测试可以发现,如上代码使用帐号名hzc,密码123456成功登陆了ftp服务器,并进入目录,打印出了pwd。
4.4.2 自动交互方法二
需求2中要求采用非交互的方式改变登录用户密码,尝试用方法1,无法实现。
这时候联想到交互信息的另一个自动输入方法,管道,通过echo + sleep + | 可以实现这个需求。
1.#!/bin/bash
2.(echo “curpassword”
3.sleep 1
4.echo “newpassword”
5.sleep 1
6.echo “newpassword”)|passwd
测试通过,运行这个脚本,直接把当前用户的curpassword改成newpassword。
4.4.3 自动交互方法三
需求3中要求自动登录root账号,尝试方法1和方法2,都出现错误提示standard in must be a tty。
这时候尝试寻找外部帮助,一个shell工具expect可以实现这个功能,其实expect就是一个专门用来实现自动交互功能的工具,expect的语法可以参考相关资料,代码如下:
1.#!/usr/bin/expect
2.spawn su root
3.expect “password: “
4.send “123456\r”
5.expect eof
6.exit
4.4.4 方法总结
方法一(重定向)简单直观,也经常有实际应用,但是在自动交互领域功能有限。方法二(管道)也很简单直观,有时甚至不用sleep配合就能展现强大的自动交互实力,但是在某些时候也束手无策。
方法三(expect)在功能上是最为强大的,expect本来就是为实现自动交互功能而生,但是缺点是需要安装expect包,在嵌入式等环境下难以安装。
4.5 Shell函数返回值
Shell函数返回值,常用的两种方式:return,echo
4.5.1 return语句
shell函数的返回值,可以和其他语言的返回值一样,通过return语句返回。
示例:
#!/bin/sh
function test()
{
echo “arg1 = $1”
if [ $1 = “1” ] ;then
return 1
else
return 0
fi
}
echo
echo “test 1”
test 1
echo $? # print return result
echo
echo “test 0”
test 0
echo $? # print return result
echo
echo “test 2”
test 2
echo $? # print return result
输出结果为:
test 1
arg1 = 1
1
test 0
arg1 = 0
0
test 2
arg1 = 2
0
先定义了一个函数test,根据它输入的参数是否为1来return 1或者return 0。
获取函数的返回值通过调用函数,或者最后执行的值获得。
另外,可以直接用函数的返回值用作if的判断。
注意:return只能用来返回整数值,且和c的区别是返回为正确,其他的值为错误。
4.5.2 echo 返回值
其实在shell中,函数的返回值有一个非常安全的返回方式,即通过输出到标准输出返回。因为子进程会继承父进程的标准输出,因此,子进程的输出也就直接反应到父进程。
示例:
#!/bin/sh
function test()
{
echo “arg1 = $1”
if [ $1 = “1” ] ;then
echo “1”
else
echo “0”
fi
}
echo
echo “test 1”
test 1
echo
echo “test 0”
test 0
echo
echo “test 2”
test 2
结果:
test 1
arg1 = 1
1
test 0
arg1 = 0
0
test 2
arg1 = 2
0
4.6 iptables
4.6.1 前言
防火墙,其实说白了讲,就是用于实现Linux下访问控制的功能的,它分为硬件的或者软件的防火墙两种。无论是在哪个网络中,防火墙工作的地方一定是在网络的边缘。而我们的任务就是需要去定义到底防火墙如何工作,这就是防火墙的策略,规则,以达到让它对出入网络的IP、数据进行检测。
目前市面上比较常见的有3、4层的防火墙,叫网络层的防火墙,还有7层的防火墙,其实是代理层的网关。
对于TCP/IP的七层模型来讲,我们知道第三层是网络层,三层的防火墙会在这层对源地址和目标地址进行检测。但是对于七层的防火墙,不管你源端口或者目标端口,源地址或者目标地址是什么,都将对你所有的东西进行检查。所以,对于设计原理来讲,七层防火墙更加安全,但是这却带来了效率更低。所以市面上通常的防火墙方案,都是两者结合的。而又由于我们都需要从防火墙所控制的这个口来访问,所以防火墙的工作效率就成了用户能够访问数据多少的一个最重要的控制,配置的不好甚至有可能成为流量的瓶颈。
二:iptables 的历史以及工作原理
1.iptables的发展:
iptables的前身叫ipfirewall (内核1.x时代),这是一个作者从freeBSD上移植过来的,能够工作在内核当中的,对数据包进行检测的一款简易访问控制工具。但是ipfirewall工作功能极其有限(它需要将所有的规则都放进内核当中,这样规则才能够运行起来,而放进内核,这个做法一般是极其困难的)。当内核发展到2.x系列的时候,软件更名为ipchains,它可以定义多条规则,将他们串起来,共同发挥作用,而现在,它叫做iptables,可以将规则组成一个列表,实现绝对详细的访问控制功能。
他们都是工作在用户空间中,定义规则的工具,本身并不算是防火墙。它们定义的规则,可以让在内核空间当中的netfilter来读取,并且实现让防火墙工作。而放入内核的地方必须要是特定的位置,必须是tcp/ip的协议栈经过的地方。而这个tcp/ip协议栈必须经过的地方,可以实现读取规则的地方就叫做 netfilter.(网络过滤器)
作者一共在内核空间中选择了5个位置,
1.内核空间中:从一个网络接口进来,到另一个网络接口去的
2.数据包从内核流入用户空间的
3.数据包从用户空间流出的
4.进入/离开本机的外网接口
5.进入/离开本机的内网接口
2.iptables的工作机制
从上面的发展我们知道了作者选择了5个位置,来作为控制的地方,但是你有没有发现,其实前三个位置已经基本上能将路径彻底封锁了,但是为什么已经在进出的口设置了关卡之后还要在内部卡呢? 由于数据包尚未进行路由决策,还不知道数据要走向哪里,所以在进出口是没办法实现数据过滤的。所以要在内核空间里设置转发的关卡,进入用户空间的关卡,从用户空间出去的关卡。那么,既然他们没什么用,那我们为什么还要放置他们呢?因为我们在做NAT和DNAT的时候,目标地址转换必须在路由之前转换。所以我们必须在外网而后内网的接口处进行设置关卡。
这五个位置也被称为五个钩子函数(hook functions),也叫五个规则链。
1.PREROUTING (路由前)
2.INPUT (数据包流入口)
3.FORWARD (转发管卡)
4.OUTPUT(数据包出口)
5.POSTROUTING(路由后)
这是NetFilter规定的五个规则链,任何一个数据包,只要经过本机,必将经过这五个链中的其中一个链。
3.防火墙的策略
防火墙策略一般分为两种,一种叫“通”策略,一种叫“堵”策略,通策略,默认门是关着的,必须要定义谁能进。堵策略则是,大门是洞开的,但是你必须有身份认证,否则不能进。所以我们要定义,让进来的进来,让出去的出去,所以通,是要全通,而堵,则是要选择。当我们定义的策略的时候,要分别定义多条功能,其中:定义数据包中允许或者不允许的策略,filter过滤的功能,而定义地址转换的功能的则是nat选项。为了让这些功能交替工作,我们制定出了“表”这个定义,来定义、区分各种不同的工作功能和处理方式。
我们现在用的比较多个功能有3个:
1.filter 定义允许或者不允许的
2.nat 定义地址转换的
3.mangle功能:修改报文原数据
我们修改报文原数据就是来修改TTL的。能够实现将数据包的元数据拆开,在里面做标记/修改内容的。而防火墙标记,其实就是靠mangle来实现的。
小扩展:
对于filter来讲一般只能做在3个链上:INPUT ,FORWARD ,OUTPUT
对于nat来讲一般也只能做在3个链上:PREROUTING ,OUTPUT ,POSTROUTING
而mangle则是5个链都可以做:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
iptables/netfilter(这款软件)是工作在用户空间的,它可以让规则进行生效的,本身不是一种服务,而且规则是立即生效的。而我们iptables现在被做成了一个服务,可以进行启动,停止的。启动,则将规则直接生效,停止,则将规则撤销。
iptables还支持自己定义链。但是自己定义的链,必须是跟某种特定的链关联起来的。在一个关卡设定,指定当有数据的时候专门去找某个特定的链来处理,当那个链处理完之后,再返回。接着在特定的链中继续检查。
注意:规则的次序非常关键,谁的规则越严格,应该放的越靠前,而检查规则的时候,是按照从上往下的方式进行检查的。
三.规则的写法:
iptables定义规则的方式比较复杂:
格式:iptables [-t table] COMMAND chain CRETIRIA -j ACTION
-t table :3个filter nat mangle
COMMAND:定义如何对规则进行管理
chain:指定你接下来的规则到底是在哪个链上操作的,当定义策略的时候,是可以省略的
CRETIRIA:指定匹配标准
-j ACTION :指定如何进行处理
比如:不允许172.16.0.0/24的进行访问。
iptables -t filter -A INPUT -s 172.16.0.0/16 -p udp –dport 53 -j DROP
当然你如果想拒绝的更彻底:
iptables -t filter -R INPUT 1 -s 172.16.0.0/16 -p udp –dport 53 -j REJECT
iptables -L -n -v #查看定义规则的详细信息
四:详解COMMAND:
1.链管理命令(这都是立即生效的)
-P :设置默认策略的(设定默认门是关着的还是开着的)
默认策略一般只有两种
iptables -P INPUT (DROP|ACCEPT) 默认是关的/默认是开的
比如:
iptables -P INPUT DROP 这就把默认规则给拒绝了。并且没有定义哪个动作,所以关于外界连接的所有规则包括Xshell连接之类的,远程连接都被拒绝了。
-F: FLASH,清空规则链的(注意每个链的管理权限)
iptables -t nat -F PREROUTING
iptables -t nat -F 清空nat表的所有链
-N:NEW 支持用户新建一个链
iptables -N inbound_tcp_web 表示附在tcp表上用于检查web的。
-X: 用于删除用户自定义的空链
使用方法跟-N相同,但是在删除之前必须要将里面的链给清空昂了
-E:用来Rename chain主要是用来给用户自定义的链重命名
-E oldname newname
-Z:清空链,及链中默认规则的计数器的(有两个计数器,被匹配到多少个数据包,多少个字节)
iptables -Z :清空
2.规则管理命令
-A:追加,在当前链的最后新增一个规则
-I num : 插入,把当前规则插入为第几条。
-I 3 :插入为第三条
-R num:Replays替换/修改第几条规则
格式:iptables -R 3 …………
-D num:删除,明确指定删除第几条规则
3.查看管理命令 “-L”
附加子命令
-n:以数字的方式显示ip,它会将ip直接显示出来,如果不加-n,则会将ip反向解析成主机名。
-v:显示详细信息
-vv
-vvv :越多越详细
-x:在计数器上显示精确值,不做单位换算
–line-numbers : 显示规则的行号
-t nat:显示所有的关卡的信息
五:详解匹配标准
1.通用匹配:源地址目标地址的匹配
-s:指定作为源地址匹配,这里不能指定主机名称,必须是IP
IP | IP/MASK | 0.0.0.0/0.0.0.0
而且地址可以取反,加一个“!”表示除了哪个IP之外
-d:表示匹配目标地址
-p:用于匹配协议的(这里的协议通常有3种,TCP/UDP/ICMP)
-i eth0:从这块网卡流入的数据
流入一般用在INPUT和PREROUTING上
-o eth0:从这块网卡流出的数据
流出一般在OUTPUT和POSTROUTING上
2.扩展匹配
2.1隐含扩展:对协议的扩展
-p tcp :TCP协议的扩展。一般有三种扩展
–dport XX-XX:指定目标端口,不能指定多个非连续端口,只能指定单个端口,比如
–dport 21 或者 –dport 21-23 (此时表示21,22,23)
–sport:指定源端口
–tcp-fiags:TCP的标志位(SYN,ACK,FIN,PSH,RST,URG)
对于它,一般要跟两个参数:
1.检查的标志位
2.必须为1的标志位
–tcpflags syn,ack,fin,rst syn = –syn
表示检查这4个位,这4个位中syn必须为1,其他的必须为0。所以这个意思就是用于检测三次握手的第一次包的。对于这种专门匹配第一包的SYN为1的包,还有一种简写方式,叫做–syn
-p udp:UDP协议的扩展
–dport
–sport
-p icmp:icmp数据报文的扩展
–icmp-type:
echo-request(请求回显),一般用8 来表示
所以 –icmp-type 8 匹配请求回显数据包
echo-reply (响应的数据包)一般用0来表示
2.2显式扩展(-m)
扩展各种模块
-m multiport:表示启用多端口扩展
之后我们就可以启用比如 –dports 21,23,80
六:详解-j ACTION
常用的ACTION:
DROP:悄悄丢弃
一般我们多用DROP来隐藏我们的身份,以及隐藏我们的链表
REJECT:明示拒绝
ACCEPT:接受
custom_chain:转向一个自定义的链
DNAT
SNAT
MASQUERADE:源地址伪装
REDIRECT:重定向:主要用于实现端口重定向
MARK:打防火墙标记的
RETURN:返回
在自定义链执行完毕后使用返回,来返回原规则链。
练习题1:
只要是来自于172.16.0.0/16网段的都允许访问我本机的172.16.100.1的SSHD服务
分析:首先肯定是在允许表中定义的。因为不需要做NAT地址转换之类的,然后查看我们SSHD服务,在22号端口上,处理机制是接受,对于这个表,需要有一来一回两个规则,如果我们允许也好,拒绝也好,对于访问本机服务,我们最好是定义在INPUT链上,而OUTPUT再予以定义就好。(会话的初始端先定义),所以加规则就是:
定义进来的: iptables -t filter -A INPUT -s 172.16.0.0/16 -d 172.16.100.1 -p tcp –dport 22 -j ACCEPT
定义出去的: iptables -t filter -A OUTPUT -s 172.16.100.1 -d 172.16.0.0/16 -p tcp –dport 22 -j ACCEPT
将默认策略改成DROP:
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
七:状态检测:
是一种显式扩展,用于检测会话之间的连接关系的,有了检测我们可以实现会话间功能的扩展
什么是状态检测?对于整个TCP协议来讲,它是一个有连接的协议,三次握手中,第一次握手,我们就叫NEW连接,而从第二次握手以后的,ack都为1,这是正常的数据传输,和tcp的第二次第三次握手,叫做已建立的连接(ESTABLISHED),还有一种状态,比较诡异的,比如:SYN=1 ACK=1 RST=1,对于这种我们无法识别的,我们都称之为INVALID无法识别的。还有第四种,FTP这种古老的拥有的特征,每个端口都是独立的,21号和20号端口都是一去一回,他们之间是有关系的,这种关系我们称之为RELATED。
所以我们的状态一共有四种:
NEW
ESTABLISHED
RELATED
INVALID
所以我们对于刚才的练习题,可以增加状态检测。比如进来的只允许状态为NEW和ESTABLISHED的进来,出去只允许ESTABLISHED的状态出去,这就可以将比较常见的反弹式木马有很好的控制机制。
对于练习题的扩展:
进来的拒绝出去的允许,进来的只允许ESTABLISHED进来,出去只允许ESTABLISHED出去。默认规则都使用拒绝
iptables -L -n –line-number :查看之前的规则位于第几行
改写INPUT
iptables -R INPUT 2 -s 172.16.0.0/16 -d 172.16.100.1 -p tcp –dport 22 -m state –state NEW,ESTABLISHED -j ACCEPT
iptables -R OUTPUT 1 -m state –state ESTABLISHED -j ACCEPT
此时如果想再放行一个80端口如何放行呢?
iptables -A INPUT -d 172.16.100.1 -p tcp –dport 80 -m state –state NEW,ESTABLISHED -j ACCEPT
iptables -R INPUT 1 -d 172.16.100.1 -p udp –dport 53 -j ACCEPT
练习题2:
假如我们允许自己ping别人,但是别人ping自己ping不通如何实现呢?
分析:对于ping这个协议,进来的为8(ping),出去的为0(响应).我们为了达到目的,需要8出去,允许0进来
在出去的端口上:iptables -A OUTPUT -p icmp –icmp-type 8 -j ACCEPT
在进来的端口上:iptables -A INPUT -p icmp –icmp-type 0 -j ACCEPT
小扩展:对于127.0.0.1比较特殊,我们需要明确定义它
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
iptables -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
八:SNAT和DNAT的实现
由于我们现在IP地址十分紧俏,已经分配完了,这就导致我们必须要进行地址转换,来节约我们仅剩的一点IP资源。那么通过iptables如何实现NAT的地址转换呢?
1.SNAT基于原地址的转换
基于原地址的转换一般用在我们的许多内网用户通过一个外网的口上网的时候,这时我们将我们内网的地址转换为一个外网的IP,我们就可以实现连接其他外网IP的功能。
所以我们在iptables中就要定义到底如何转换:
定义的样式:
比如我们现在要将所有192.168.10.0网段的IP在经过的时候全都转换成172.16.100.1这个假设出来的外网地址:
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -j SNAT –to-source 172.16.100.1
这样,只要是来自本地网络的试图通过网卡访问网络的,都会被统统转换成172.16.100.1这个IP.
那么,如果172.16.100.1不是固定的怎么办?
我们都知道当我们使用联通或者电信上网的时候,一般它都会在每次你开机的时候随机生成一个外网的IP,意思就是外网地址是动态变换的。这时我们就要将外网地址换成 MASQUERADE(动态伪装):它可以实现自动寻找到外网地址,而自动将其改为正确的外网地址。所以,我们就需要这样设置:
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -j MASQUERADE
这里要注意:地址伪装并不适用于所有的地方。
2.DNAT目标地址转换
对于目标地址转换,数据流向是从外向内的,外面的是客户端,里面的是服务器端通过目标地址转换,我们可以让外面的ip通过我们对外的外网ip来访问我们服务器不同的服务器,而我们的服务却放在内网服务器的不同的服务器上。
如何做目标地址转换呢?:
iptables -t nat -A PREROUTING -d 192.168.10.18 -p tcp –dport 80 -j DNAT –todestination 172.16.100.2
目标地址转换要做在到达网卡之前进行转换,所以要做在PREROUTING这个位置上
九:控制规则的存放以及开启
注意:你所定义的所有内容,当你重启的时候都会失效,要想我们能够生效,需要使用一个命令将它保存起来
1.service iptables save 命令
它会保存在/etc/sysconfig/iptables这个文件中
2.iptables-save 命令
iptables-save > /etc/sysconfig/iptables
3.iptables-restore 命令
开机的时候,它会自动加载/etc/sysconfig/iptabels
如果开机不能加载或者没有加载,而你想让一个自己写的配置文件(假设为iptables.2)手动生效的话:
iptables-restore < /etc/sysconfig/iptables.2
则完成了将iptables中定义的规则手动生效
4.6.2 总结
Iptables是一个非常重要的工具,它是每一个防火墙上几乎必备的设置,也是我们在做大型网络的时候,为了很多原因而必须要设置的。学好Iptables,可以让我们对整个网络的结构有一个比较深刻的了解,同时,我们还能够将内核空间中数据的走向以及linux的安全给掌握的非常透彻。我们在学习的时候,尽量能结合着各种各样的项目,实验来完成,这样对你加深iptables的配置,以及各种技巧有非常大的帮助。
附加iptables比较好的文章:
netfilter/iptables全攻略
4.7 shell调试
4.7.1 使用trap
trap [command] signal
4.7.1.0.1 信号
信号名 何时产生
EXIT 从一个函数中退出或整个脚本执行完毕
ERR 当一条命令返回非零状态时(代表命令执行不成功)
DEBUG 脚本中每一条命令执行之前
4.7.2 使用tee命令
ifconfig eth0 | cut –f 3 | tee temp.txt
4.7.3 使用调试函数
DEBUG()
4.7.4 调试选项
-n 只读取shell脚本,但不实际执行
-x 进入跟踪方式,显示所执行的每一条命令
-c “string” 从strings中读取命令
“-n”可用于测试shell脚本是否存在语法错误,但不会实际执行命令。在shell脚本编写完成之后,实际执行之前,首先使用“-n”选项来测试脚本是否存在语法错误是一个很好的习惯。因为某些shell脚本在执行时会对系统环境产生影响,比如生成或移动文件等,如果在实际执行才发现语法错误,您不得不手工做一些系统环境的恢复工作才能继续测试这个脚本。
“-c”选项使shell解释器从一个字符串中而不是从一个文件中读取并执行shell命令。当需要临时测试一小段脚本的执行结果时,可以使用这个选项,如下所示:
sh -c ‘a=1;b=2;let c=$a+$b;echo “c=$c”‘
“-x”选项可用来跟踪脚本的执行,是调试shell脚本的强有力工具。“-x”选项使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来,并且在行首显示一个”+”号。 “+”号后面显示的是经过了变量替换之后的命令行的内容,有助于分析实际执行的是什么命令。 “-x”选项使用起来简单方便,可以轻松对付大多数的shell调试任务,应把其当作首选的调试手段。
4.7.5 对”-x”选项的增强
“-x”执行选项是目前最常用的跟踪和调试shell脚本的手段,但其输出的调试信息仅限于进行变量替换之后的每一条实际执行的命令以及行首的一个”+”号提示符,居然连行号这样的重要信息都没有,对于复杂的shell脚本的调试来说,还是非常的不方便。幸运的是,我们可以巧妙地利用shell内置的一些环境变量来增强”-x”选项的输出信息,下面先介绍几个shell内置的环境变量:
$BASH_SOURCE
当前脚本名称
$LINENO
代表shell脚本的当前行号,类似于C语言中的内置宏__LINE__
$FUNCNAME
函数的名字,类似于C语言中的内置宏__func__,但宏__func__只能代表当前所在的函数名,而$FUNCNAME的功能更强大,它是一个数组变量,其中包含了整个调用链上所有的函数的名字,故变量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,而变量${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字,余者可以依此类推。
$PS4
主提示符变量$PS1和第二级提示符变量$PS2比较常见,但很少有人注意到第四级提示符变量$PS4的作用。我们知道使用“-x”执行选项将会显示shell脚本中每一条实际执行过的命令,而$PS4的值将被显示在“-x”选项输出的每一条命令的前面。在Bash Shell中,缺省的$PS4的值是”+”号。(现在知道为什么使用”-x”选项时,输出的命令前面有一个”+”号了吧?)。
利用$PS4这一特性,通过使用一些内置变量来重定义$PS4的值,我们就可以增强”-x”选项的输出信息。例如先执行export PS4=’+{$LINENO:${FUNCNAME[0]}} ‘, 然后再使用“-x”选项来执行脚本,就能在每一条实际执行的命令前面显示其行号以及所属的函数名。
以下是一个存在bug的shell脚本的示例,本文将用此脚本来示范如何用“-n”以及增强的“-x”执行选项来调试shell脚本。这个脚本中定义了一个函数isRoot(),用于判断当前用户是不是root用户,如果不是,则中止脚本的执行
$ cat –n exp4.sh
1 #!/bin/bash
2 isRoot()
3 {
4 if [ “$UID” -ne 0 ]
5 return 1
6 else
7 return 0
8 fi
9 }
10 isRoot
11 if [“$?” -ne 0 ]
12 then
13 echo “Must be root to run this script”
14 exit 1
15 else
16 echo “welcome root user”
17 #do something
18 fi
首先执行sh –n exp4.sh来进行语法检查,输出如下:
$ sh –n exp4.sh
exp4.sh: line 6: syntax error near unexpected token `else’
exp4.sh: line 6: ` else’
发现了一个语法错误,通过仔细检查第6行前后的命令,我们发现是第4行的if语句缺少then关键字引起的(写惯了C程序的人很容易犯这个错误)。我们可以把第4行修改为if [ “$UID” -ne 0 ]; then来修正这个错误。再次运行sh –n exp4.sh来进行语法检查,没有再报告错误。接下来就可以实际执行这个脚本了,执行结果如下:
$ sh exp4.sh
exp2.sh: line 11: [1: command not found
welcome root user
尽管脚本没有语法错误了,在执行时却又报告了错误。错误信息还非常奇怪“[1: command not found”。现在我们可以试试定制$PS4的值,并使用“-x”选项来跟踪:
$ export PS4=’+{$LINENO:${FUNCNAME[0]}} ‘
$ sh –x exp4.sh
+{10:} isRoot
+{4:isRoot} ‘[‘ 503 -ne 0 ‘]’
+{5:isRoot} return 1
+{11:} ‘[1’ -ne 0 ‘]’
exp4.sh: line 11: [1: command not found
+{16:} echo ‘welcome root user’
welcome root user
从输出结果中,我们可以看到脚本实际被执行的语句,该语句的行号以及所属的函数名也被打印出来,从中可以清楚的分析出脚本的执行轨迹以及所调用的函数的内部执行情况。由于执行时是第11行报错,这是一个if语句,我们对比分析一下同为if语句的第4行的跟踪结果:
+{4:isRoot} ‘[‘ 503 -ne 0 ‘]’
+{11:} ‘[1’ -ne 0 ‘]’
可知由于第11行的[号后面缺少了一个空格,导致[号与紧挨它的变量$?的值1被shell解释器看作了一个整体,并试着把这个整体视为一个命令来执行,故有“[1: command not found”这样的错误提示。只需在[号后面插入一个空格就一切正常了。
shell中还有其它一些对调试有帮助的内置变量,比如在Bash Shell中还有BASH_SOURCE, BASH_SUBSHELL等一批对调试有帮助的内置变量,您可以通过man sh或man bash来查看,然后根据您的调试目的,使用这些内置变量来定制$PS4,从而达到增强“-x”选项的输出信息的目的。
4.7.6 总结
现在让我们来总结一下调试shell脚本的过程:
首先使用“-n”选项检查语法错误,然后使用“-x”选项跟踪脚本的执行,使用“-x”选项之前,别忘了先定制PS4变量的值来增强“-x”选项的输出信息,至少应该令其输出行号信息(先执行export PS4=’+[$LINENO]’,更一劳永逸的办法是将这条语句加到您用户主目录的.bash_profile文件中去),这将使你的调试之旅更轻松。也可以利用trap,调试钩子等手段输出关键调试信息,快速缩小排查错误的范围,并在脚本中使用“set -x”及“set +x”对某些代码块进行重点跟踪。这样多种手段齐下,相信您已经可以比较轻松地抓出您的shell脚本中的臭虫了。如果您的脚本足够复杂,还需要更强的调试能力,可以使用shell调试器bashdb,这是一个类似于GDB的调试工具,可以完成对shell脚本的断点设置,单步执行,变量观察等许多功能,使用bashdb对阅读和理解复杂的shell脚本也会大有裨益。关于bashdb的安装和使用,不属于本文范围,您可参阅http://bashdb.sourceforge.net/上的文档并下载试用。
4.8 shell多行注释
4.8.1 法一
: ‘
语句1
语句2
语句3
语句4
‘
例如:
linux101:/home/wsj # sh dian
ni
ni
ni
ni
ni
ni
linux101:/home/wsj # more dian
#!/bin/ksh
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
: ‘
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
‘
4.8.2 法二
if false; then
语句1
语句2
语句3
语句4
fi
linux101:/home/wsj # sh dian
ni
ni
ni
ni
ni
ni
linux101:/home/wsj # more dian
#!/bin/ksh
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
if false; then
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
echo “ni”
fi
4.8.3 法三
: << 字符 #这里的字符可以是数字或者是字符都可以
语句1
语句2
语句3
语句4
字符
4.8.4 法四
((0)) & {
语句1
语句2
语句3
}
4.9 expect
4.9.1 warning
[warning: here-document at line 17 delimited by end-of-file (wanted `EOF’) ](http://blog.csdn.net/woyuditan26/article/details/41516359)
原因是末尾的EOF后面带有空格,EOF前后都不应有空格或其他符号
4.9.2 expect实例详解****
1. expect 是基于tcl 演变而来的,所以很多语法和tcl 类似,基本的语法如下
所示:
1.1 首行加上/usr/bin/expect
1.2 spawn: 后面加上需要执行的shell 命令,比如说spawn sudo touch testfile
1.3 expect: 只有spawn 执行的命令结果才会被expect 捕捉到,因为spawn 会启
动一个进程,只有这个进程的相关信息才会被捕捉到,主要包括:标准输入的提示信息,eof 和timeout。
1.4 send 和send_user:send 会将expect 脚本中需要的信息发送给spawn 启动
的那个进程,而send_user 只是回显用户发出的信息,类似于shell 中的echo 而
已。
2. 一个小例子,用于linux 下账户的建立:
filename: account.sh,可以使用./account.sh newaccout 来执行;
1 #!/usr/bin/expect
2
3 set passwd “mypasswd”
4 set timeout 60
5
6 if {$argc != 1} {
7 send “usage ./account.sh $newaccount\n”
8 exit
9 }
10
11 set user [lindex $argv [expr $argc-1]]
12
13 spawn sudo useradd -s /bin/bash -g mygroup -m $user
14
15 expect {
16 “assword” {
17 send_user “sudo now\n”
18 send “$passwd\n”
19 exp_continue
20 }
21 eof
22 {
23 send_user “eof\n”
24 }
25 }
26
27 spawn sudo passwd $user
28 expect {
29 “assword” {
30 send “$passwd\n”
31 exp_continue
32 }
33 eof
34 {
35 send_user “eof”
36 }
37 }
38
39 spawn sudo smbpasswd -a $user
40 expect {
41 “assword” {
42 send “$passwd\n”
43 exp_continue
44 }
45 eof
46 {
47 send_user “eof”
48 }
49 }
3. 注意点:
第3 行: 对变量赋值的方法;
第4 行: 默认情况下,timeout 是10 秒;
第6 行: 参数的数目可以用$argc 得到;
第11 行:参数存在$argv 当中,比如取第一个参数就是[lindex $argv 0];并且
如果需要计算的话必须用expr,如计算2-1,则必须用[expr 2-1];
第13 行:用spawn 来执行一条shell 命令,shell 命令根据具体情况可自行调整;
有文章说sudo 要加-S,经过实际测试,无需加-S 亦可;
第15 行:一般情况下,如果连续做两个expect,那么实际上是串行执行的,用。expect 与“{ ”之间直接必须有空格或则TAB间隔,否则会出麻烦,会报错invalid command name “expect{“** **
例子中的结构则是并行执行的,主要是看匹配到了哪一个;在这个例子中,如果
你写成串行的话,即
expect “assword”
send “$passwd\n”
expect eof
send_user “eof”
那么第一次将会正确运行,因为第一次sudo 时需要密码;但是第二次运行时由于
密码已经输过(默认情况下sudo 密码再次输入时间为5 分钟),则不会提示用户
去输入,所以第一个expect 将无法匹配到assword,而且必须注意的是如果是
spawn 命令出现交互式提问的但是expect 匹配不上的话,那么程序会按照timeout
的设置进行等待;可是如果spawn 直接发出了eof 也就是本例的情况,那么expect
“assword”将不会等待,而直接去执行expect eof。
这时就会报expect: spawn id exp6 not open,因为没有spawn 在执行,后面的
expect 脚本也将会因为这个原因而不再执行;所以对于类似sudo 这种命令分支
不定的情况,最好是使用并行的方式进行处理;
第17 行:仅仅是一个用户提示而已,可以删除;
第18 行:向spawn 进程发送password;
第19 行:使得spawn 进程在匹配到一个后再去匹配接下来的交互提示;
第21 行:eof 是必须去匹配的,在spawn 进程结束后会向expect 发送eof;如果
不去匹配,有时也能运行,比如sleep 多少秒后再去spawn 下一个命令,但是不
要依赖这种行为,很有可能今天还可以,明天就不能用了;
4. 其他
下面这个例子比较特殊,在整个过程中就不能expect eof 了:
1 #!/usr/bin/expect
2
3 set timeout 30
4 spawn ssh 10.192.224.224
5 expect “password:”
6 send “mypassword\n”
7 expect “*$”
8 send “mkdir tmpdir\n” #远程执行命令用send发送,不用spawn
9 expect “$” #注意这个地方,要与操作系统上环境变量PS1相匹配,尤其是有PS1有空格的情况下,一定在expct “$ “把空格加上,加不上你就完蛋了。我试过。
这个例子实际上是通过ssh 去登录远程机器,并且在远程机器上创佳一个目录,
我们看到在我们输入密码后并没有去expect eof,这是因为ssh 这个spawn 并没
有结束,而且手动操作时ssh 实际上也不会自己结束除非你exit;所以你只能
expect bash 的提示符,当然也可以是机器名等,这样才可以在远程创建一个目
录。
注意,请不要用spawn mkdir tmpdir,这样会使得上一个spawn 即ssh 结束,那
么你的tmpdir 将在本机建立。
当然实际情况下可能会要你确认ssh key,可以通过并行的expect 进行处理,不
多赘述。
\5. 觉得bash 很多情况下已经很强大,所以可能用expect 只需要掌握这些就好了,
其他的如果用到可以再去google 了。
源代码图片:
6 \实例:下面这个脚本是完成对单个服务器scp任务。
1: #!/usr/bin/expect
2:
3: set timeout 10
4: set host [lindex $argv 0]
5: set username [lindex $argv 1]
6: set password [lindex $argv 2]
7: set src_file [lindex $argv 3]
8: set dest_file [lindex $argv 4]
9:
10: spawn scp $src_file $username@$host:$dest_file
11: expect {
12: “(yes/no)?”
13: {
14: send “yes\n”
15: expect “*assword:” { send “$password\n”}
16: }
17: “*assword:”
18: {
19: send “$password\n”
20: }
21: }
22: expect “100%”
23: expect eof
参考源代码图片:
注意代码刚开始的第一行,指定了expect的路径,与shell脚本相同,这一句指定了程序在执行时到哪里去寻找相应的启动程序。代码刚开始还设定了timeout的时间为10秒,如果在执行scp任务时遇到了代码中没有指定的异常,则在等待10秒后该脚本的执行会自动终止。
spawn代表在本地终端执行的语句,在该语句开始执行后,expect开始捕获终端的输出信息,然后做出对应的操作。expect代码中的捕获的(yes/no)内容用于完成第一次访问目标主机时保存密钥的操作。有了这一句,scp的任务减少了中断的情况。代码结尾的expect eof与spawn对应,表示捕获终端输出信息的终止。
有了这段expect的代码,还只能完成对单个远程主机的scp任务。如果需要实现批量scp的任务,则需要再写一个shell脚本来调用这个expect脚本。
1: #!/bin/sh
2:
3: list_file=$1
4: src_file=$2
5: dest_file=$3
6:
7: cat $list_file | while read line
8: do
9: host_ip=echo $line | awk '{print $1}'
10: username=echo $line | awk '{print $2}'
11: password=echo $line | awk '{print $3}'
12: echo “$host_ip”
13: ** ./expect_scp $host_ip $username $password $src_file $dest_file**
15: done
参考代码图片如下:
很简单的代码,指定了3个参数:列表文件的位置、本地源文件路径、远程主机目标文件路径。需要说明的是其中的列表文件指定了远程主机ip、用户名、密码,这些信息需要写成以下的格式:
IP username password
中间用空格或tab键来分隔,多台主机的信息需要写多行内容。
这样就指定了两台远程主机的信息。注意,如果远程主机密码中有“$”、“#”这类特殊字符的话,在编写列表文件时就需要在这些特殊字符前加上转义字符,否则expect在执行时会输入错误的密码。
对于这个shell脚本,保存为batch_scp.sh文件,与刚才保存的expect_scp文件和列表文件(就定义为hosts.list文件吧)放到同一目录下,执行时按照以下方式输入命令就可以了:
4.10 Bash内建变量
4.10.1 $BASH
这个变量将指向Bash的二进制执行文件的位置.
4.10.2 $BASH_ENV
这个环境变量将指向一个Bash启动文件,这个启动文件将在调用一个脚本时被读取
4.10.3 $BASH_SUBSHELL
这个变量将提醒subshell的层次,这是一个在version3才被添加到Bash中的新特性
4.10.4 $BASH_VERSINFO[n]
记录Bash安装信息的一个6元素的数组.与下边的$BASH_VERSION很像,但这个更加详细.
# BASH_VERSINFO[0] = 3 # 主版本号
# BASH_VERSINFO[1] =00 # 次版本号
# BASH_VERSINFO[2] = 14 # Patch 次数.
# BASH_VERSINFO[3] = 1 # Build version.
# BASH_VERSINFO[4] = release # Release status.
# BASH_VERSINFO[5] = i386-redhat-Linux-gnu # Architecture
4.10.5 $BASH_VERSION
安装在系统上的Bash的版本号.
使用这个变量对于判断系统上到底运行的是那个shell来说是一种非常好的办法.$SHELL,有时将不能给出正确的答案.
4.10.6 $DIRSTACK
在目录栈中最上边的值(将受到pushd和popd的影响).这个内建的变量与dirs命令是保持一致的,但是dirs命令将显示目录栈的整个内容.
4.10.7 $EDITOR
脚本调用的默认编辑器,一般是vi或者是emacs.
4.10.8 $EUID
“effective”用户ID号,当前用户被假定的任何id号.可能在su命令中使用.
注意:$EUID并不一定与$UID相同.
4.10.9 $FUNCNAME
当前函数的名字.
4.10.10 $GLOBIGNORE
一个文件名的模式匹配列表,如果在file globbing中匹配到的文件包含这个列表中的
某个文件,那么这个文件将被从匹配到的文件中去掉.
4.10.11 $GROUPS
当前用户属于的组.
这是一个当前用户的组id列表(数组),就像在/etc/passwd中记录的一样.
4.10.12 $HOME
用户的home目录,一般都是/home/username
4.10.13 $HOSTNAME
hostname命令将在一个init脚本中,在启动的时候分配一个系统名字.,gethostname()函数将用来设置这个$HOSTNAME内部变量
4.10.14 $HOSTTYPE
主机类型,就像$MACHTYPE,识别系统的硬件.
4.10.15 $IFS
内部域分隔符,这个变量用来决定Bash在解释字符串时如何识别域,或者单词边界.
$IFS默认为空白(空格,tab,和新行),但可以修改,比如在分析逗号分隔的数据文件时.
4.10.16 $IGNOREEOF
忽略EOF: 告诉shell在log out之前要忽略多少文件结束符(control-D).
4.10.17 $LC_COLLATE
常在.bashrc或/etc/profile中设置,这个变量用来在文件名扩展和模式匹配校对顺序.
如果$LC_COLLATE被错误的设置,那么将会在filename globbing中引起错误的结果.
注意:在2.05以后的Bash版本中,filename globbing将不在对[]中的字符区分大小写.
4.10.18 $LC_CTYPE
这个内部变量用来控制globbing和模式匹配的字符串解释.
4.10.19 $LINENO
这个变量记录它所在的shell脚本中它所在行的行号.这个变量一般用于调试目的
4.10.20 $MACHTYPE
系统类型,提示系统硬件
4.10.21 $OLDPWD
老的工作目录(“OLD-print-working-directory”,你所在的之前的目录)
4.10.22 $OSTYPE
操作系统类型.
4.10.23 $PATH
指向Bash外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等.
当给出一个命令时,Bash将自动对$PATH中的目录做一张hash表.$PATH中以”:”分隔的
目录列表将被存储在环境变量中.一般的,系统存储的$PATH定义在/ect/processed或PATH=${PATH}:/opt/bin将把/opt/bin目录附加到$PATH变量中.在脚本中,这是一个添加目录到$PATH中的便捷方法.这样在这个脚本退出的时候,$PATH将会恢复(因为这个shell是个子进程,像这样的一个脚本是不会将它的父进程的环境变量修改的)
4.10.24 $PIPESTATUS
数组变量将保存最后一个运行的前台管道的退出码.有趣的是,这个退出码和最后一个命令运行的退出码并不一定相同.
4.10.25 $PPID
一个进程的$PPID就是它的父进程的进程id(pid).[1],使用pidof命令对比一下
4.10.26 $PROMPT_COMMAND
这个变量保存一个在主提示符($PS1)显示之前需要执行的命令.
4.10.27 $PS1
主提示符,具体见命令行上的显示.
4.10.28 $PS2
第2提示符,当你需要额外的输入的时候将会显示,默认为”>”.
4.10.29 $PS3
第3提示符,在一个select循环中显示
4.10.30 $PS4
第4提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边,默认为”+”.
4.10.31 $PWD
工作目录(你当前所在的目录).与pwd内建命令作用相同.
4.10.32 $REPLY
read命令如果没有给变量,那么输入将保存在$REPLY中.在select菜单中也可用,但是只提供选择的变量的项数,而不是变量本身的值. $REPLY是’read’命令结果保存的默认变量
4.10.33 $SECONDS
这个脚本已经运行的时间(单位为秒).
4.10.34 $SHELLOPTS
这个变量里保存shell允许的选项,这个变量是只读的.
4.10.35 $SHLVL
Shell层次,就是shell层叠的层次,如果是命令行那$SHLVL就是1,如果命令行执行的脚本中,$SHLVL就是2,以此类推
4.10.36 $TMOUT
如果$TMOUT环境变量被设置为一个非零的时间值,那么在过了这个指定的时间之后,shell提示符将会超时,这会引起一个logout.,在2.05b版本的Bash中,已经支持在一个带有read命令的脚本中使用$TMOUT变量.
4.10.37 其他
Bash shell中的位置参数$#,$*,$@,$0,$1,$2…及特殊参数$?,$-等的含义
在Bash shell中经常会见到一些比较特殊的符号
位置参数:
$1, $2, $3等等…
位置参数,从命令行传递给脚本,或者是传递给函数.或者赋值给一个变量.此数目可以任意多,但只有前9个可以被访问,使用shift命令可以改变这个限制。
$0表示当前执行的进程名,script 本身的名字,或者在正则表达式中表示整行输出
$#
命令行或者是位置参数的个数.(见Example 33-2)
$*
所有的位置参数,被作为一个单词.注意:”$*”必须被””引用.
$@
与$*同义,但是每个参数都是一个独立的””引用字串,这就意味着参数被完整地传递,
并没有被解释和扩展.这也意味着,每个参数列表中的每个参数都被当成一个独立的单词.
注意:”$@”必须被””引用.
其他的特殊参数
$-
传递给脚本的falg(使用set 命令).参考Example 11-15.
显示shell使用的当前选项,与set命令功能相同
注意:这起初是ksh 的特征,后来被引进到Bash 中,但不幸的是,在Bash 中它看上去也不
能可靠的工作.使用它的一个可能的方法就是让这个脚本进行自我测试(查看是否是交
互的).
$!
在后台运行的最后的工作的PID(进程ID).
$_
保存之前执行的命令的最后一个参数.
$?
命令,函数或者脚本本身的退出状态(见Example 23-7)
用于检查上一个命令,函数或者脚本执行是否正确。(在Linux中,命令退出状态为0表示该命令正确执行,任何非0值表示命令出错。)
$$
脚本自身的进程ID.这个变量经常用来构造一个”unique”的临时文件名.
(参考Example A-13,Example 29-6,Example 12-28 和Example 11-25).
这通常比调用mktemp 来得简单.
注意事项:
[1] 当前运行的脚本的PID 为$$.
[2] “argument”和”parameter”这两个单词经常不加区分的使用.在这整本书中,这两个
单词的意思完全相同.(在翻译的时候就未加区分,统统翻译成参数)
4.11 字体颜色
#!/bin/bash
#
#下面是字体输出颜色及终端格式控制
#字体色范围:30-37
echo -e “\033[30m 黑色字 \033[0m”
echo -e “\033[31m 红色字 \033[0m”
echo -e “\033[32m 绿色字 \033[0m”
echo -e “\033[33m 黄色字 \033[0m”
echo -e “\033[34m 蓝色字 \033[0m”
echo -e “\033[35m 紫色字 \033[0m”
echo -e “\033[36m 天蓝字 \033[0m”
echo -e “\033[37m 白色字 \033[0m”
#字背景颜色范围:40-47
echo -e “\033[40;37m 黑底白字 \033[0m”
echo -e “\033[41;30m 红底黑字 \033[0m”
echo -e “\033[42;34m 绿底蓝字 \033[0m”
echo -e “\033[43;34m 黄底蓝字 \033[0m”
echo -e “\033[44;30m 蓝底黑字 \033[0m”
echo -e “\033[45;30m 紫底黑字 \033[0m”
echo -e “\033[46;30m 天蓝底黑字 \033[0m”
echo -e “\033[47;34m 白底蓝字 \033[0m”
第 5 章 makefile
5.1 基本语法
变量名可以使用字母、数字和下划线。makefile中的第一个目标会被作为默认目标。
Makefile文件中相应的命令行前一定要有一个制表符。****
5.1.1 makefile规则
目标:依赖
命令
[1] 隐式规则
所有.o文件都可自动由.c文件使用命令$(CC) $(CPPFLAGS) $(CFLAGS) –c file.c –o file.o生成。注意:隐式规则只能查找相同文件名,不同后缀的文件。
隐式规则目录:
c编译器:.c变为.o $(CC) –c $(CPPFLAGS) $(CFLAGS)
c++ : .cc或.C变为.o $(CXX) –c $(CPPFLAGS) $(CXXFLAGS)
[2] 模式规则
5.1.2 变量
[1] 预定义变量
AR 库文件维护程序名称 默认值为ar
CC c编译器的名称 默认值为gcc
CXX c编译器的名称 默认值为g++
CFLAGS c编译器选项 无默认值
CXXFLAGS c++编译器选项 无默认值
[2] 简单变量:
定义:变量名:=[文本]
添加:变量名+=[文本]
object := main.o
object += import.o output.o
引用:$(变量名) c=gcc
${变量名} $c
$单字符变量 cc=gcc $(cc)
[3] 自动变量/内置变量
在makefile中,存在系统默认的自动化变量
$^:用空格分开的所有的依赖文件
$@:当前目标的名称(代表目标列表)
$?:比当前目标更新的已修改的依赖性列表(代表所有已更新的依赖文件)
$<:比当前目标更新的已修改的当前依赖性名称(代表第一个依赖文件)
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
例:hello: main.o func1.o func2.o
gcc main.o func1.o func2.o -o hello
=》
hello: main.o func1.o func2.o
gcc $^ -o $@
[4] 其他
目标 依赖 命令
main.o: main.c
gcc –c main.c
makefile例:
#!/bin/bash
hello:hello.o
gcc hello.o -o hello
hello.o:hello.c
gcc -c hello.c
clean:
rm *.o hello
5.1.3 函数
5.1.4 伪目标/虚目标
Makefile中把那些没有任何依赖只有执动作的目标称为“伪目标”(phony targets)。
常见虚目标列表:
all、clean、install、uninstall。
若本地已有目标文件则要用.PHONY
.PHONY :clean
clean :
rm –f hellomain.o func1.ofunc2.o
“.PHONY” 将“clean”目标声明为伪目标,虚目标允许同名文件存在
5.1.5 makefile常见语法错误
[1] 缺少TAB键
可使用 cat -t makefile 查看TAB使用情况
其中使用了TAB键的以“^I”显示
[2] 在连接符’\’和换行符之间插入了空格
可用 cat -e makefile 查看空格使用情况,一般结尾时以$结束
也可用 grep ‘\[此处填一空格 ]$’ makefile 显示makefile中不正确的行
5.1.6 命令行的使用和调试
[1] 使用非标准的makefile名称文件
make -f prog1.makefile
[2] 从标准输入读取
make -f -
[3] 显示makefile中所执行命令的顺序
make -n
5.1.7 制作工程文件的makefile
一般工程文件proc由以下部分组成:
[1] src:make.c fun1.c fun2.c
[2] include:fun1.h fun2.h fun2.h导出函数
[3] makefile
VPATH = src:./headers
VPATH:指定依赖文件的搜索路径
src 第一搜索路径
./headers 第二搜索路径
5.1.8 嵌套执行make
subsystem:
cd subdir && $(MAKE)
5.1.9 命令出错
如果要忽略命令出错,我们可以在makefile的命令行前加一个减号“-”(在tab键之后),标记为不管命令是否出错都认为是成功的。
5.1.10 杂项
Makefile中“#”字符后的内容被视作注释。
hello:hello.c
@gcc hello.c –o hello
5.2 其他
5.2.1 定义宏
DEFINES += -DTEST_DEF
5.2.2 取消宏
DEFINES += -UTEST_DEF
5.2.3 注意TAB不能用4个空格代替
5.2.4 if语句
ifeq($VAR, “TEST”)
else ifeq($VAR,”TEST2”)
5.2.5 得到已定义的宏
make –p >macro.txt 可以得到一些数据定义等
5.3 项目构建Make,Automake,CMake
在本系列文章《C实战:强大的程序调试工具GDB》中我们简要学习了流行的调试工具GDB的使用方法。本文继续“C实战”的主题,对同样非常流行的构建工具
5.3.1 Automake
Automake其实是一系列工具集Autotools中的一员,要想发挥Automake的威力,需要配合使用Autotools中的其他工具,例如autoscan、aclocal、autoconf和autoheader。在下面的Automake构建流程中,能看到这些工具的身影。
autoscan:生成configure.scan
configure.in:将configure.scan重命名为configure.in后,修改内容。重点是AM_INIT_AUTOMAKE和AC_CONFIG_FILES两项,如果没配置的话,下一步的aclocal是无法产生aclocal.m4的
aclocal:生成aclocal.m4
autoconf:生成configure
autoheader:生成config.h.in,使程序可移植
Makefile.am:手动编写Makefile.am。bin_PROGRAMS指定最终生成可执行文件的名称,helloworld_SOURCES指定所有源文件
NEWS AUTHORS README ChangeLog:手动创建
automake:执行automake -a生成Makefile.in
configure:执行./configure生成Makefile
# Step 1:
[root@vm automaketest]# autoscan
# Step 2:
[root@vm automaketest]# mv configure.scan configure.in
[root@vm automaketest]# cat configure.in
# -- Autoconf --
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.63])
AC_INIT(main, 1.0)
AM_INIT_AUTOMAKE(main, 1.0)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
# Step 3:
[root@vm automaketest]# aclocal
# Step 4:
[root@vm automaketest]# autoconf
# Step 5:
[root@vm automaketest]# autoheader
# Step 6:
[root@vm automaketest]# cat Makefile.am
bin_PROGRAMS=main
main_SOURCES=main.c hello.c
# Step 7:
[root@vm automaketest]# touch NEWS README AUTHORS ChangeLog
# Step 8:
[root@vm automaketest]# automake -a
configure.in:6: installing ‘./install-sh’
configure.in:6: installing ‘./missing’
Makefile.am: installing ‘./INSTALL’
Makefile.am: installing ‘./COPYING’ using GNU General Public License v3 file
Makefile.am: Consider adding the COPYING file to the version control system
Makefile.am: for your code, to avoid questions about which license your project uses.
Makefile.am: installing ‘./depcomp’
# Step 9:
[root@BC-VM-edce4ac67d304079868c0bb265337bd4 automaketest]# ./configure
checking for a BSD-compatible install… /usr/bin/install -c
checking whether build environment is sane… yes
checking for a thread-safe mkdir -p… /bin/mkdir -p
checking for gawk… gawk
checking whether make sets $(MAKE)… yes
checking for gcc… gcc
checking for C compiler default output file name… a.out
checking whether the C compiler works… yes
checking whether we are cross compiling… no
checking for suffix of executables…
checking for suffix of object files… o
checking whether we are using the GNU C compiler… yes
checking whether gcc accepts -g… yes
checking for gcc option to accept ISO C89… none needed
checking for style of include used by make… GNU
checking dependency style of gcc… gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands**
这样Makefile就生成好了,看一下当前目录发现已经这么多文件了!如果想清理一下怎么办呢?其实Automake早为我们想好了,它生成的Makefile功能很多:
make:编译源代码,生成目标文件
make clean:清理之前make产生的临时文件
make install:将编译好的可执行文件安装到系统目录,一般为/usr/local/bin
make dist:生成软件发布包,将可执行文件及相关文件打包成”PACKAGE-VERSION.tar.gz”的tarball。其中PACKAGE和VERSION可以在configure.in中通过AM_INIT_AUTOMAKE(PACKAGE, VERSION)定义。对于我们的例子,执行后会生成main-1.0.tar.gz
make distcheck:查看发布包是否正确,解压开执行configure和make来确认
make distclean:不仅将make生的文件,同时将configure生成的文件也都删除,包括Makefile
[root@vm automaketest]# make dist
{ test ! -d “main-1.0” || { find “main-1.0” -type d ! -perm -200 -exec chmod u+w {} ‘;’ && rm -fr “main-1.0”; }; }
test -d “main-1.0” || mkdir “main-1.0”
test -n “” \
|| find “main-1.0” -type d ! -perm -755 \
-exec chmod u+rwx,go+rx {} ; -o \
! -type d ! -perm -444 -links 1 -exec chmod a+r {} ; -o \
! -type d ! -perm -400 -exec chmod a+r {} ; -o \
! -type d ! -perm -444 -exec /bin/sh /root/Temp/automaketest/install-sh -c -m a+r {} {} ; \
|| chmod -R a+r “main-1.0”
tardir=main-1.0 && /bin/sh /root/Temp/automaketest/missing –run tar chof - “$tardir” | GZIP=–best gzip -c >main-1.0.tar.gz
{ test ! -d “main-1.0” || { find “main-1.0” -type d ! -perm -200 -exec chmod u+w {} ‘;’ && rm -fr “main-1.0”; }; }
[root@vm automaketest]# tree -L 1
.
├── aclocal.m4
├── AUTHORS
├── autom4te.cache
├── autoscan.log
├── ChangeLog
├── config.h
├── config.h.in
├── config.log
├── config.status
├── configure
├── configure.in
├── COPYING -> /usr/share/automake-1.11/COPYING
├── depcomp -> /usr/share/automake-1.11/depcomp
├── hello.c
├── hello.h
├── INSTALL -> /usr/share/automake-1.11/INSTALL
├── install-sh -> /usr/share/automake-1.11/install-sh
├── main.c
├── main-1.0.tar.gz
├── Makefile
├── Makefile.am
├── Makefile.in
├── missing -> /usr/share/automake-1.11/missing
├── NEWS
├── README
└── stamp-h1
1 directory, 24 files
[root@vm automaketest]# make distclean
test -z “main” || rm -f main
rm -f *.o
rm -f *.tab.c
test -z “” || rm -f
test . = “.” || test -z “” || rm -f
rm -f config.h stamp-h1
rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
rm -f config.status config.cache config.log configure.lineno config.status.lineno
rm -rf ./.deps
rm -f Makefile
测试一下,看看Automake生成的Makefile是否能正常工作。
[root@vm automaketest]# make
make all-am
make[1]: Entering directory ‘/root/Temp/automaketest’
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT hello.o -MD -MP -MF .deps/hello.Tpo -c -o hello.o hello.c
mv -f .deps/hello.Tpo .deps/hello.Po
gcc -g -O2 -o main main.o hello.o
make[1]: Leaving directory ‘/root/Temp/automaketest’
[root@vm automaketest]# ./main
Hello, Make!
5.3.2 CMake
前面我们已经见识了Automake的强大和复杂。现在我们重新用CMake生成Makefile,Automake中的9步被压缩到了只需要2步!
编写CMakeLists.txt
执行cmake .
5.3.2.1 CMakeLists.txt
对于我们示例中这种简单的项目,CMakeLists.txt简单得不能再简单了。指定好项目名称和最终生成的可执行文件名称后,就完成了!
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (main)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(main ${DIR_SRCS})
5.3.2.2 cmakes
现在执行cmake .就能得到一个CMake为我们自动生成的Makefile。这个Makefile比我们手写的要复杂得多,这里就不深入分析了。除了Makefile外,CMake还产生了一些缓存文件和临时文件,目前还不清楚具体是做什么的。
[root@vm cmaketest]# cmake .
– The C compiler identification is GNU 4.4.7
– The CXX compiler identification is GNU 4.4.7
– Check for working C compiler: /usr/bin/cc
– Check for working C compiler: /usr/bin/cc – works
– Detecting C compiler ABI info
– Detecting C compiler ABI info - done
– Check for working CXX compiler: /usr/bin/c++
– Check for working CXX compiler: /usr/bin/c++ – works
– Detecting CXX compiler ABI info
– Detecting CXX compiler ABI info - done
– Configuring done
– Generating done
– Build files have been written to: /root/Temp/cmaketest
[root@vm cmaketest]# tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── CMakeLists.txt
├── hello.c
├── hello.h
├── main.c
└── Makefile
1 directory, 7 files
[root@vm cmaketest]# make
Scanning dependencies of target main
[ 50%] Building C object CMakeFiles/main.dir/main.c.o
[100%] Building C object CMakeFiles/main.dir/hello.c.o
Linking C executable main
[100%] Built target main**
5.3.3 附:参考资料
5.3.4 CMake快速使用教程
5.3.4.1 实例HelloWorld
首先创建一个test1文件夹,里面创建一个main.c文件,内容如下:
#include <stdio.h>
int main()
{ printf(“Hello World!\n”);
return 0;}
再创建一个CMakeLists.txt
PROJECT (HELLO)
SET(SRC_LIST main.c)
ADD_EXECUTABLE(hello ${SRC_LIST})
第一行:设置项目名称;
第二行:将SRC_LIST值设置为main.c
第三行:生成可执行文件 hello。 ${} 是引用某个值。
Terminal中cd进入到test1目录,创建一个build目录用于外部构建(编译所产生的文件都生成在build目录),依次执行下面三条命令:
cmake ..
make
./hello
得到的结果如下:
如果要引用内部库的话,比如是关于SDL和opengl的程序,需要在CMakeLists.txt中添加
TARGET_LINK_LIBRARIES(hello SDL)
TARGET_LINK_LIBRARIES(hello GLU)
对应终端的编译命令就是:
-lSDL -lGLU
若不是引用内部库,则需要将相应目录添加进来,用到的是INCLUDE_DIRECTORIES命令。
5.3.4.1.1 库的构建与安装
这次我们的目标是:
1,建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出
Hello World 字符串。
2,安装头文件与共享库。
目录安排如下:
build-用于外部编译;
libhello-hello库的源文件;
src-主程序
首先看libhello里的文件:
/filename:hello.h/
#ifndef DBZHANG_HELLO_
#define DBZHANG_HELLO_
void hello(const char* name);
#endif //DBZHANG_HELLO_
/filename:hello.c/
#include <stdio.h>
#include “hello.h”
void hello(const char * name)
{
printf (“Hello %s!\n”, name);
}
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
set(LIB_SRC hello.c)
add_library(libhello STATIC ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME “hello”)
install(TARGETS libhello
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
install(FILES hello.h DESTINATION include/hello)
src文件夹
/filename:main.c/
#include “hello.h”
int main()
{
hello(“Jack”);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(APP_SRC main.c)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(main ${APP_SRC})
target_link_libraries(main libhello)
最外面的CMakeLists.txt
project(HELLO)
add_subdirectory(src)
add_subdirectory(libhello)
解释:
除build目录外每一个目录都要建立一个CMakeLists.txt.
生成库的语句:add_library(libhello STATIC ${LIB_SRC})
这条语句是建立静态库,若要建立动态库的话将STATIC改成SHARED.
install命令负责库的安装。
make一下,结果就像这样:
再sudo make install.
安装好库之后,我们在想使用hello方法的时候,只要添加头文件#include就可以了,编译的时候
g++ main .c -o main –lhello
5.3.5 Automake快速上手
GNU make允许将一个软件项目的代码分开放在不同的源文件里,有改动的时候可以只对改动的文件重新编译,然后重新连接,这种编译管理方法提高了生成目标的效率。make要调用一个makefile文件来实现。
Makefile的编写是使用make的关键问题。当工程里面包含的很多源文件,库,文件放在不同的子目录时,手动书写makefile文件不方便且容易出错。一般情况下我们用autoconf和automake自动生成makefile文件。
5.3.5.1 自动生成makefile流程
如图所示为automake,autoconf生成makefile的过程(简化)。
程序源码
|
autoscan*
|
configure.scan
|
编译修改*
|
makefile.am configure.in –aclocal*–> aclocal.m4
\ ____ / \ __________ /
\ / \ /
automake* autoconf*
| |
makefile.in configure
\ ____ /
\ /
./configure*
|
makefile
为一个项目源文件生成makefile并make的步骤如下:
操作在包含源文件的项目目录下进行。
运行autoscan,生成文件configure.scan。
修改configure.scan,改名为configure.in。
运行autoheader,生成文件configure.h.in(现在一般改为configure.ac)。configure.in里有宏AC_CONFIG_HEADER()时用。
运行libtoolize,生成一些支持文件,ltmain.sh。需要用libtool生成共享库用。
运行allocal,生成aclocal.m4。
运行autoconf,生成configure。
为源文件编写makefie.am,每一个包含源文件的目录和子目录都有一个makefile.am。
运行automake,生成makefile.in,每个包含makefile.am的子目录都生成makefile.in。
automake -a选项可以补齐文件config.guess,config.sub,install-sh,missing,depcomp。
运行./configure,生成config.status,config.h,makefile。
运行make,生成中间文件对象文件,库文件,最后生成可执行文件。
运行make install,相应的可执行文件,库文件,头文件拷贝到系统相应位置。
5.3.5.2 自动生成makefile例子
这个例子共有三个C文件,main.c,add/add.c和sub/sub.c。源代码如下:
/main.c/
#include
int main(void)
{
printf(“%d\n”,add(sub(100,5),1));
return 0;
}
/* add/add.c */
int add(int x,int y)
{
return x+y;
}
/* sub/sub.c */
int sub(int x,int y)
{
return x-y;
}
这个例子中add.c和sub.c被编译成动态连接库,然后main.c与这两个库连接生成可执行文件。
1.手动输入configure.in和makefile.am
Q:自动生成makefile需要手动输入什么文件,作用是什么?
按照上面的步骤执行,需要手动输入的文件只有两类configure.in和makefile.am。
(1).手动修改configure.in
autoscan运行后configure.scan文件为(系统不一样可能略有会不同)
# -- Autoconf --
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.63])
AC_INIT([FULL-PACKAGE-NAME],[VERSION],[BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT
手动修改为configure.in:
# -- Autoconf --
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.63])
AC_INIT(hellobb,1.0,[])
AM_INIT_AUTOMAKE(hellobb,1.0)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_LIBTOOL
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT(Makefile add/Makefile sub/Makefile)
其中宏AC_INIT和AC_OUTPUT是必需的,AC_INIT放在开头,AC_OUTPUT放在结尾。
AC_INIT:说明软件包的名字,版本等。
AC_OUTPUT:说明生成的shell脚本文件configure运行后输出的文件。
AM_INIT_AUTOMAKE:用automake需要的宏。
AC_PROG_CC:决定要使用的C编译器。如果环境变量CC没有值,检查gcc和cc,别的C编译器。设置变量CC的值为找到的编译器名称。
AC_PROG_LIBTOOL:检查LIBTOOL。
AC_CONFIG_SRCDIR([main.c]):./configure检查在给它的目录里是否有main.c文件。
AC_CONFIG_HEADER([config.h]):./configure从config.h.in中生成config.h文件,config.h文件是包含了很多#define宏的c头文件。当编译文件的时候,用一个宏-DHAVE_CONFIG_H代替原来需要用-Dmacro传递的所有预处理宏集合。
例如屏蔽掉这句#AC_CONFIG_HEADER([config.h]),make时编译main.c的命令是
gcc -DPACKAGE_NAME="hellobb" -DPACKAGE_TARNAME="hellobb" -DPACKAGE-VERSION="1.0" -DPACKAGE_STRING="hellobb\ 1.0" -DPACKAGE_BUGREPORT="" -DPACKAGE="hellobb" -DVERSION="1.0" -DSTDC_HEADERS=1 _DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTDLIB_H=1 -DHAVE_STING_H=1 -DHAVE_MEMORY_H=1 -dHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR="./libs/" -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.c main.c
如果启用AC_CONFIG_HEADER([config.h]),make时编译main.c的命令是
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
上面那些宏-Dmacro都包含在config.h里了,我们只需要给编译器传递-DHAVE_CONFIG_H就行了。
(2). 手动输入makefile.am
每个包含源文件的子目录下都需要一个makefile.am。
add/makefile.am是(sub/makefile.am类似):
lib_LTLIBRARIES = libadd.la //生成共享库libadd.la
libadd_la_SOURCES = add.c //共享库libadd.la依赖的源文件
项目根目录下的makefile.am是:
AUTOMAKE_OPTIONS = foreign
SUBDIRS = add sub //子目录,递归处理子目录的makefile.am
bin_PROGRAMS = main //生成可执行文件main
main_SOURCES = main.c //可执行文 件main依赖的源文件
main_LDADD = add/libadd.la sub/libsub.la //可执行文件main连接时需要的库文件
(3).automake时运行automake -a:
automake -a即 automake -add-missing:如果目录下缺少文件config.guess,config.sub,missing,depcomp,install-sh,automake会自动从系统中获取这些文件,这里是创建这些文件,让它们是指向系统中对应文件的连接文件。
2.过程中的输入输出文件
Q:./configure需要什么输入文件,生成什么文件?
Q:make需要什么输入文件,中间生成的文件放哪?
为了更清楚整个处理过程autoscan,autoheader,automake,autoconf做了什么事情,观察每一步执行过程中需要的输入文件和产生的输出文件。
输入源文件:main.c add/add.c sub/sub.c
输入makefile.am :makefile.am add/makefile.am sub/makefile.am
autoscan :configure.scan
autoscan.log
手动修改:(入:configure.scan)
configure.in //指出生成的包名字和版本号,./configure输出文件,配置预处理头文件,
指出需要检查的程序、头文件、类型和结构、库函数等。
autoheader :config.h.in
libtoolize :ltmain.sh
aclocal :aclocal.m4 //autoconf相关的宏,在本地m4中定义好的。
autom4te.cache
autoconf : (入:configure.in, aclocal.m4)
configure
automake -a :(入:configure.in makefile.am add/makefile.am sub/makefile.am)
config.guess //猜测出一个规范的系统名字。
config.sub //把平台和系统别名映射成规范形式:
CPU_TYPE_MANUFACTORER-KERNEL-OPERATING-SYSTEM。
install-sh //安装程序,脚本或数据文件。
missing // Common stub for a few missing GNU program while installing.
depcomp //Compile a program generating depedencies as side-effects.
makefile.in add/makefile.in sub/makefile.in
./configure :(入:configure makefile.in add/makefile.in sub/makefile.in config.h.in )
(入:config.guess config.sub install-sh missing depcomp ltmainsh)
config.status //configure运行时找到的系统相关变量都存放在这里,
./configure的最后就是运行shell脚本config.status
config.h //包含编译时要传给编译器的所有预处理宏
config.log //包含了./configure运行时生成的所有信息,供调试时查看
makefile add/makefile sub/makefile
./deps/main.Po add/.deps/add.Plo sub/.deps.sub.Plo //空的dummy依赖文件
stamp-h1
make :(入:config.status config.h makefile add/makefile sub/makefile)
libtool
add.lo add.o libadd.la
add/.libs下add.o libadd.a libadd.la libsub.lai libadd.so libadd.so.0 libadd.so.0.0.0
sub.lo sub.o libsub.la
sub/.libs下sub.o libsub.a libsub.la libsub.lai libsub.so libsub.so.0 libsub.so.0.0.0
./deps/main.Po add/.deps/add.Plo sub/.deps.sub.Plo //更新依赖文件,由于gcc有-MT,-MD,-MP,-MF选项
main.o
main
3.configure和make执行过程
Q:./configure都作了些什么事情,输入的文件都用来做什么,config.status的作用?
Q:make的运行过程,生成的文件放在哪里有谁来指定?
configure运行
configure是一个shell脚本文件,由autoconf生成,它自动为源码包配置编译连接选项,适应不同的硬件平台和POSIX操作系统,输出所需要的Makefile。
上面的例子中./configure运行期间输出如下图。
configure.in中宏对configure的影响:
宏AC_INIT,AM_INIT_AUTOMAKE,AC_PROG_CC对应图中的白色部分。
宏AC_PROG_LIBTOOL对应图中的灰色部分。
宏AC_OUTPUT在图中的桃红色部分。
桃红色部分为最后configure生成config.status文件,并执行它。configure主管检查你的系统,把结果存放到config.status中,config.status根据它的检查结果实际执行正确的动作。
configure检查与系统相关的一系列变量,这些变量存储到文件config.status中,供makefile调用。这些变量包括编译连接时需要的程序,这些程序在系统中的位置(目录),调用这些程序的选项,比如编译器的目录,编译器的选项-g是否支持等。
configure能猜出它运行的系统的规范名字cpu-vendor-os,它通过运行脚本文件config.guess输出变量uname来猜出。
configure能识别很多系统名字的别名,它通过运行脚本文件config.sub把系统名字变成规范名字。
# ./configure
checking for a BSD-compatible install… /usr/bin/install -c
checking whether build environment is sane… yes
checking for a thread-safe mkdir -p… /bin/mkdir -p
checking for gawk… gawk
checking whether make sets $(MAKE)… yes
checking for gcc… gcc
checking for C compiler default output file name… a.out
checking whether the C compiler works… yes
checking whether we are cross compiling… no
checking for suffix of executables…
checking for suffix of object files… o
checking whether we are using the GNU C compiler… yes
checking whether gcc accepts -g… yes
checking for gcc option to accept ISO C89… none needed
checking for style of include used by make… GNU
checking dependency style of gcc… gcc3
checking build system type… i686-pc-linux-gnu
checking host system type… i686-pc-linux-gnu
checking how to print strings… printf
checking for a sed that does not truncate output… /bin/sed
checking for grep that handles long lines and -e… /bin/grep
checking for egrep… /bin/grep -E
checking for fgrep… /bin/grep -F
checking for ld used by gcc… /usr/bin/ld
checking if the linker (/usr/bin/ld) is GNU ld… yes
checking for BSD- or MS-compatible name lister (nm)… /usr/bin/nm -B
checking the name lister (/usr/bin/nm -B) interface… BSD nm
checking whether ln -s works… yes
checking the maximum length of command line arguments… 1966080
checking whether the shell understands some XSI constructs… yes
checking whether the shell understands “+=”… yes
checking for /usr/bin/ld option to reload object files… -r
checking for objdump… objdump
checking how to recognize dependent libraries… pass_all
checking for ar… ar
checking for strip… strip
checking for ranlib… ranlib
checking command to parse /usr/bin/nm -B output from gcc object… ok
checking how to run the C preprocessor… gcc -E
checking for ANSI C header files… yes
checking for sys/types.h… yes
checking for sys/stat.h… yes
checking for stdlib.h… yes
checking for string.h… yes
checking for memory.h… yes
checking for strings.h… yes
checking for inttypes.h… yes
checking for stdint.h… yes
checking for unistd.h… yes
checking for dlfcn.h… yes
checking for objdir… .libs
checking if gcc supports -fno-rtti -fno-exceptions… no
checking for gcc option to produce PIC… -fPIC -DPIC
checking if gcc PIC flag -fPIC -DPIC works… yes
checking if gcc static flag -static works… no
checking if gcc supports -c -o file.o… yes
checking if gcc supports -c -o file.o… (cached) yes
checking whether the gcc linker (/usr/bin/ld) supports shared libraries… yes
checking whether -lc should be explicitly linked in… no
checking dynamic linker characteristics… GNU/Linux ld.so
checking how to hardcode library paths into programs… immediate
checking whether stripping libraries is possible… yes
checking if libtool supports shared libraries… yes
checking whether to build shared libraries… yes
checking whether to build static libraries… yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating add/Makefile
config.status: creating sub/Makefile
config.status: creating config.h
config.status: executing depfiles commands
config.status: executing libtool commands
make运行
make运行期间输出如下图。
makefile.am对makefile的影响:
它根据SUBDIRS = add sub让make递归进入每个子目录处理子目录的Makefile。
根据main_LDADD = add/libadd.la sub/libsub.la为main连接libadd.la和libsub.la库。
configure.in对makefile的影响:
根据AC_PROG_LIBTOOL让libtool完成编译连接工作。
根据AC_CONFIG_HEADERS([config.h])只需传递预处理宏-DHAVE_CONFIG_H给编译器。
makefile中很多与系统相关的信息都是通过变量获取的,这些变量之前已经由configure检查好存放在config.status里面,预处理宏存放在config.h里面。比如我们要用到的编译器CC,编译器选项CFLAGS等。makefile中的变量完成替换后,开始实际执行命令,它会递归执行每一个子目录下的makefile,生成对象文件,连接库文件,最后连接成可执行文件。
# make
make all-recursive
make[1]: Entering directory /root/program/hello_autoconf2' Making all in add make[2]: Entering directory
/root/program/hello_autoconf2/add’
/bin/sh ../libtool –tag=CC –mode=compile gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT add.lo -MD -MP -MF .deps/add.Tpo -c -o add.lo add.c
libtool: compile: gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT add.lo -MD -MP -MF .deps/add.Tpo -c add.c -fPIC -DPIC -o .libs/add.o
libtool: compile: gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT add.lo -MD -MP -MF .deps/add.Tpo -c add.c -o add.o >/dev/null 2>&1
mv -f .deps/add.Tpo .deps/add.Plo
/bin/sh ../libtool –tag=CC –mode=link gcc -g -O2 -o libadd.la -rpath /usr/local/lib add.lo
libtool: link: gcc -shared .libs/add.o -Wl,-soname -Wl,libadd.so.0 -o .libs/libadd.so.0.0.0
libtool: link: (cd “.libs” && rm -f “libadd.so.0” && ln -s “libadd.so.0.0.0” “libadd.so.0”)
libtool: link: (cd “.libs” && rm -f “libadd.so” && ln -s “libadd.so.0.0.0” “libadd.so”)
libtool: link: ar cru .libs/libadd.a add.o
libtool: link: ranlib .libs/libadd.a
libtool: link: ( cd “.libs” && rm -f “libadd.la” && ln -s “../libadd.la” “libadd.la” )
make[2]: Leaving directory /root/program/hello_autoconf2/add' Making all in sub make[2]: Entering directory
/root/program/hello_autoconf2/sub’
/bin/sh ../libtool –tag=CC –mode=compile gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT sub.lo -MD -MP -MF .deps/sub.Tpo -c -o sub.lo sub.c
libtool: compile: gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT sub.lo -MD -MP -MF .deps/sub.Tpo -c sub.c -fPIC -DPIC -o .libs/sub.o
libtool: compile: gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT sub.lo -MD -MP -MF .deps/sub.Tpo -c sub.c -o sub.o >/dev/null 2>&1
mv -f .deps/sub.Tpo .deps/sub.Plo
/bin/sh ../libtool –tag=CC –mode=link gcc -g -O2 -o libsub.la -rpath /usr/local/lib sub.lo
libtool: link: gcc -shared .libs/sub.o -Wl,-soname -Wl,libsub.so.0 -o .libs/libsub.so.0.0.0
libtool: link: (cd “.libs” && rm -f “libsub.so.0” && ln -s “libsub.so.0.0.0” “libsub.so.0”)
libtool: link: (cd “.libs” && rm -f “libsub.so” && ln -s “libsub.so.0.0.0” “libsub.so”)
libtool: link: ar cru .libs/libsub.a sub.o
libtool: link: ranlib .libs/libsub.a
libtool: link: ( cd “.libs” && rm -f “libsub.la” && ln -s “../libsub.la” “libsub.la” )
make[2]: Leaving directory /root/program/hello_autoconf2/sub' make[2]: Entering directory
/root/program/hello_autoconf2’
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
/bin/sh ./libtool –tag=CC –mode=link gcc -g -O2 -o main main.o add/libadd.la sub/libsub.la
libtool: link: gcc -g -O2 -o .libs/main main.o add/.libs/libadd.so sub/.libs/libsub.so -Wl,-rpath -Wl,/usr/local/lib
make[2]: Leaving directory /root/program/hello_autoconf2' make[1]: Leaving directory
/root/program/hello_autoconf2’
Q: 编译main.c的命令:gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c 中,选项-MT,-MD,-MP,-MF什么意思?
选项-MT,-MD,-MP,-MF是跟依赖关系文件相关的,相当于往文件里加makefile格式的rule,简化形式:
targets : dependency。
-MF .deps/main.Tpo 指定把依赖关系的rule写到文件.deps/main.Tpo 里,跟-MD一起用,如果跟-M一起用将在预处理后结束不编译。
-MT 指定target。
-MP 在依赖关系文件中加入每个依赖文件的phony target。如
test.o:test.c test.h
test.h: #phony target
这里生成的依赖文件main.Po,add.Plo如图。
main.Po:
main.o: main.c /usr/include/stdio.h /usr/include/features.h
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h
/usr/lib/gcc/i686-redhat-linux/4.5.1/include/stddef.h
/usr/include/bits/types.h /usr/include/bits/typesizes.h
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h
/usr/lib/gcc/i686-redhat-linux/4.5.1/include/stdarg.h
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h
/usr/include/bits/stdio.h
/usr/include/stdio.h:
/usr/include/features.h:
/usr/include/sys/cdefs.h:
/usr/include/bits/wordsize.h:
/usr/include/gnu/stubs.h:
/usr/include/gnu/stubs-32.h:
/usr/lib/gcc/i686-redhat-linux/4.5.1/include/stddef.h:
/usr/include/bits/types.h:
/usr/include/bits/typesizes.h:
/usr/include/libio.h:
/usr/include/_G_config.h:
/usr/include/wchar.h:
/usr/lib/gcc/i686-redhat-linux/4.5.1/include/stdarg.h:
/usr/include/bits/stdio_lim.h:
/usr/include/bits/sys_errlist.h:
/usr/include/bits/stdio.h:
add.Plo:
add.lo: add.c
交叉编译 Cross-compiling
Q:为别的平台编译可执行程序怎么做?
交叉编译就是在目前的平台上为别的目标平台生成可执行程序或库。可以在运行configure时通过–build,–host,–target参数实现交叉编译。默认情况下 target<=host<=build<=config.guess给出的平台名称。
例如./configure –build=i686-pc-linux-gnu –host=m68k-coff。
–build=build-type :configure和compile软件包的系统类型。默认情况等于config.guess给出的系统类型。
–host=host-type :运行软件包的系统类型。默认情况等于build类型
–target=target-type :很少用,默认情况等于host类型。
交叉编译时,如果编译器,连接器,汇编器名字不是以host_type为前缀,configure都会发出警告。
要搭建交叉变异环境,如交叉编译用的编译器,连接器,汇编器跟本地的不一样,一般以host_type为前缀,如arm-pc-linux-gcc。
安装目录
Q:make install时,文件都安装到哪里去了?
prefix:安装目录的前缀。默认情况下/usr/local 。
bindir:安装时拷贝可执行文件到此目录。默认情况下/usr/local/bin 。
includir:安装时拷贝头文件到此目录。默认情况下/usr/local/include 。
libdir:安装时拷贝库文件到此目录。默认情况下/usr/local/libs 。
定制自己的安装目录,可以–prefix 和 –exec-prefix 给configure。
例如:./configure –prefix=/usr 。
GUN build system
autoconf
automake
libtool
autoheader
检查你的系统安装了以下软件:(whereis ,which)
GNU gcc
GNU make
GNU automake
GNU Autoconf
GNU m4
perl
GNU tar
GNU zip ( gzip )
GNU Libtool ( 如果你需要產生 shared library )
参考:
automake,陳雍穆,http://netlab.cse.yzu.edu.tw/~armor/columns/automake/automake.htm
Autoconf,http://www.gnu.org/software/autoconf/manual/autoconf.html
GNU Automake,http://www.gnu.org/software/automake/manual/automake.html
5.3.6 Myself tool
参考上述资料整理成一个脚本自动生成。
5.4 Makefile调试方法
5.4.1 执行选项
- -n选项
make –n常用于调试makefile,该选项可以实现只打印执行过程,而不具体执行。
- -p选项
–print-datebase
–warn-undefined-variables
-debug=all
-debug=all,basic
5.4.2 echo显示
5.5 附件
第 6 章 Linux应用程序开发
Linux是多任务、支持多种文件系统、采用虚拟内存管理技术、良好的可移植性、设备独立性、丰富的网络功能、提供全部源代码。
6.1 Linux文件编程
6.1.1 系统调用文件访问
6.1.1.1 创建
Ø int creat(const char *filename, mode_t mode)
filename:要创建的文件名(包含路径,缺省为当前路径)
mode:创建模式
常见创建模式:
S_IRUSR 可读
S_IWUSR 可写
S_IXUSR 可执行
S_IRWXU 可读、写、执行
可执行 1
v可写 2
v可读 4
v上述值的和,如可写可读 6
*#include <stdio.h> ***
*#include <stdlib.h> ***
*#include <sys/types.h> ***
*#include <sys/stat.h> ***
*#include <fcntl.h> ***
*void create_file(char *filename){ ***
/*创建的文件具有什么样的属性?/** ***
if(creat(filename,0755)<0){ ***
printf("create file %s failure!\n",filename); ***
exit(EXIT_FAILURE); ***
}else{ ***
printf("create file %s success!\n",filename); ***
} ***
*} ***
*int main(int argc,char *argv[]){ ***
int i; ***
if(argc<2){ ***
perror("you haven't input the filename,please try again!\n"); ***
exit(EXIT_FAILURE); ***
} ***
for(i=1;i<argc;i++){ ***
create_file(argv[i]); ***
} ***
exit(EXIT_SUCCESS); ***
}**
6.1.1.2 打开
int open(const char *pathname, int flags)
int open(const char *pathname, int flags, mode_t mode)
pathname:要打开的文件名(包含路径,缺省为当前路径)
flags:打开标志
常见的打开标志:
O_RDONLY 只读方式打开
O_WRONLY 只写方式打开
O_RDWR 读写方式打开
O_APPEND 追加方式打开
O_CREAT 创建一个文件
O_NOBLOCK 非阻塞方式打开
如果使用了O_CREATE标志,则使用的函数是:
int open(const char *pathname,int flags, mode_t mode); 这时需要指定mode来表示文件的访问权限。
阻塞就是干不完不准回来,
非组赛就是你先干,我现看看有其他事没有,完了告诉我一声
6.1.1.3 关闭
当我们操作完文件以后,需要关闭文件:int close(int fd)
fd: 文件描述符
6.1.1.4 读
int read(int fd, const void *buf, size_t length)
功能:从文件描述符fd所指定的文件中读取length个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。
注: read函数只是一个通用的读文件设备的接口。是否阻塞需要由设备的属性和设定所决定。一般来说,读字符终端、网络的socket描述字,管道文件等,这些文件的缺省read都是阻塞的方式。如果是读磁盘上的文件,一般不会是阻塞方式的。但使用锁和fcntl设置取消文件O_NOBLOCK状态,也会产生阻塞的read效果。
file_cp.c
#include <sys/types.h>**
#include <sys/stat.h>**
#include <fcntl.h>**
#include <stdio.h>**
*#include <errno.h> ***
*#define BUFFER_SIZE 1024 ***
*int main(int argc,char **argv) ***
*{ ***
**int from_fd,to_fd; ***
**int bytes_read,bytes_write; ***
**char buffer[BUFFER_SIZE]; ***
**char *ptr; ***
**if(argc!=3) ***
**{ ***
** **fprintf(stderr,”Usage:%s fromfile tofile/n/a”,argv[0]); ***
** **exit(1); ***
**} ***
**/* 打开源文件 */ ***
**if((from_fd=open(argv[1],O_RDONLY))==-1) ***
**{ ***
** **fprintf(stderr,”Open %s Error:%s/n”,argv[1],strerror(errno)); ***
** **exit(1); ***
**} ***
**/* 创建目的文件 */ ***
**if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1) ***
**{ ***
** **fprintf(stderr,”Open %s Error:%s/n”,argv[2],strerror(errno)); ***
** **exit(1); ***
**} ***
**/* 以下代码是一个经典的拷贝文件的代码 */ ***
**while(bytes_read=read(from_fd,buffer,BUFFER_SIZE)) ***
**{ ***
** **/* 一个致命的错误发生了 */ ***
** **if((bytes_read==-1)&&(errno!=EINTR)) break; ***
** **else if(bytes_read>0) ***
** **{ ***
** ** **ptr=buffer; ***
** ** **while(bytes_write=write(to_fd,ptr,bytes_read)) ***
** ** **{ ***
** ** ** **/* 一个致命错误发生了 */ ***
** ** ** **if((bytes_write==-1)&&(errno!=EINTR))break; ***
** ** ** **/* 写完了所有读的字节 */ ***
** ** ** **else if(bytes_write==bytes_read) break; ***
** ** ** **/* 只写了一部分,继续写 */ ***
** ** ** **else if(bytes_write>0) ***
** ** ** **{ ***
** ** ** **ptr+=bytes_write; ***
** ** ** **bytes_read-=bytes_write; ***
** ** ** **} ***
** ** **} ***
** ** **/* 写的时候发生的致命错误 */ ***
** ** **if(bytes_write==-1)break; ***
** **} ***
**} ***
**close(from_fd); ***
**close(to_fd); ***
**exit(0); ***
}**
6.1.1.5 写
int write(int fd, const void *buf, size_t length)
功能:把length个字节从buf指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。
6.1.1.6 read 与 fread 的区别的误解
缓冲
6.1.1.7 读写二进制文件
#include
#include
#include
using namespace std;**
struct Point**
{ string user;**
- string password;};***
int main()**
{* //写入文件***
ofstream file;***
file.open(“user.bin”,ios::out|ios::binary); ***
Point user1;***
user1.user=”admin”;***
user1.password=”admin”; ***
file.write((char *)&user1,sizeof(Point)); ***
file.close();***
//读取文件***
ifstream rfile;***
rfile.open(“user.bin”,ios::in|ios::binary);***
Point user2;***
//while(true)***
{rfile.read((char *) &user2,sizeof(Point));***
** if(!rfile)*
rfile.close();} cout<<user2.user<<”,”<<user2.password<<endl;***
system(“pause”);***
return 0;***
}**
6.1.1.8 定位
intlseek(int fd, offset_t offset, int whence)
功能:将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置。
whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR:相对文件读写指针的当前位置
SEEK_END:相对文件末尾
offset可取负值,表示向前移动。例如下述调用可将文件指针相对当前位置向前移动5个字节:lseek(fd, -5, SEEK_CUR)
由于lseek函数的返回值为文件指针相对于文件头的位置,因此下面调用的返回值就是文件的长度:lseek(fd, 0, SEEK_END)
6.1.1.9 文件锁定
6.1.1.9.1 flock()
int flock(int fd,int operation);
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h> /包含头文件。/
main()
{
int fd,i; /定义变量。/
char path[]=”/root/txt1.txt”; /要访问的文件。/
extern int errno; /定义错误号。/
fd=open(path,O_WRONLY|O_CREAT); /打开文件。/
if(fd!=-1) /打开成功。/
{
printf(“opened file %s .\n”,path);
printf(“please input a number to lock the file.\n”); /输入一个数字。/
scanf(“%d”,&i); /输入。/
if(flock(fd,LOCK_EX)==0) /锁定文件。/
{
printf(“the file was locked.\n”); /输出信息。/
}
else
{
printf(“the file was not locked.\n”); /文件锁定失败。/
}
printf(“please input a number to unlock the file.\n”); /提示输入。/
scanf(“%d”,&i); /输入。/
if(flock(fd,LOCK_UN)==0) /解除文件锁定。/
{
printf(“the file was unlocked.\n”); /输出解除锁定成功。/
}
else
{
printf(“the file was not unlocked.\n”); /输出解除锁定失败。/
}
close(fd); /关闭文件。/
}
else
{
printf(“cant’t open file %s.\n”,path); /不能打开文件的情况。/
printf("errno:%d\n",errno); /*显示错误号。*/
printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/
}
}
6.1.1.9.2 fcntl()
文件锁包括建议性锁和强制性锁。建议性锁要求每个上锁文件的进程都要检查是否有锁存在。
其中lockf()用于对文件施加建议性锁,而fcntl()不仅可以施加建议性锁,也可以施加强制性锁。同时fcntl()还能对文件的某一记录上锁,也就是记录锁。
记录锁又分为读取锁和写入锁。读取锁又称共享锁,能够使多个进程能在同一部分建立读取锁。写入锁又称排斥锁,任何时刻只能有一个进程在文件的某个部分建立写入锁。
int fcntl(int fd, int cmd, struct flock *lock);
lock的结构
struct flock
{ short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;}
6.1.1.9.3 lockf()
6.1.1.10 文件的移动与复制
文件的复制,是打开源文件—读取内容—写入目标文件
errno!=EINTR
注意read()如果读到数据为0,那么就表示文件结束了,如果在读的过程中遇到了中断那么会返回-1,同时置errno为EINTR。
6.1.1.10.1 文件复制1
#include <sys/types.h>**
#include <sys/stat.h>**
#include <fcntl.h>**
#include <stdio.h>**
*#include <errno.h> ***
*#define BUFFER_SIZE 1024 ***
*int main(int argc,char **argv) ***
*{ ***
*int from_fd,to_fd; ***
*int bytes_read,bytes_write; ***
*char buffer[BUFFER_SIZE]; ***
*char *ptr; ***
*if(argc!=3) ***
*{ ***
*fprintf(stderr,”Usage:%s fromfile tofile/n/a”,argv[0]); ***
*exit(1); ***
*} ***
*/* 打开源文件 */ ***
*if((from_fd=open(argv[1],O_RDONLY))==-1) ***
*{ ***
*fprintf(stderr,”Open %s Error:%s/n”,argv[1],strerror(errno)); ***
*exit(1); ***
*} ***
*/* 创建目的文件 */ ***
*if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1) ***
*{ ***
*fprintf(stderr,”Open %s Error:%s/n”,argv[2],strerror(errno)); ***
*exit(1); ***
*} ***
*/* 以下代码是一个经典的拷贝文件的代码 */ ***
*while(bytes_read=read(from_fd,buffer,BUFFER_SIZE)) ***
*{ ***
*/* 一个致命的错误发生了 */ ***
*if((bytes_read==-1)&&(errno!=EINTR)) break; ***
*else if(bytes_read>0) ***
*{ ***
*ptr=buffer; ***
*while(bytes_write=write(to_fd,ptr,bytes_read)) ***
*{ ***
*/* 一个致命错误发生了 */ ***
*if((bytes_write==-1)&&(errno!=EINTR))break; ***
*/* 写完了所有读的字节 */ ***
*else if(bytes_write==bytes_read) break; ***
*/* 只写了一部分,继续写 */ ***
*else if(bytes_write>0) ***
*{ ***
*ptr+=bytes_write; ***
*bytes_read-=bytes_write; ***
*} ***
*} ***
*/* 写的时候发生的致命错误 */ ***
*if(bytes_write==-1)break; } ***
*} ***
*close(from_fd); ***
*close(to_fd); ***
*exit(0); ***
}**
6.1.1.10.2 文件复制2
#include <stdio.h>
#include <errno.h> /包含头文件。/
main()
{
char path[]=”/root/a.txt”; /原文件。/
char path1[]=”/root/a11.txt”; /目录文件。/
char newpath[]=”/tmp/b.txt”; /一个不存在的文件。/
extern int errno; /错误号。/
if(rename(path,newpath)==0) /移动文件。/
{
printf(“the file %s was moved to %s .\n”,path,newpath); /移动成功。/
}
else
{
printf(“can’t move the file %s .\n”,path); /显示移动失败。/
printf("errno:%d\n",errno); /*显示错误号。*/
printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/
}
if(rename(path1,newpath)==0) /移动一个不存在的文件。/
{
printf(“the file %s was moved to %s .\n”,path1,newpath); /显示结果。/
}
else
{
printf(“can’t move the file %s .\n”,path1); /显示移动不成功能。/
printf("errno:%d\n",errno); /*显示错误号。*/
printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/
}
}
6.1.1.11 同一分区中文件移动函数rename
int rename(char *oldpath,char *newpath);
#include <stdio.h>
#include <errno.h> /包含头文件。/
main()
{
char path[]=”/root/a.txt”; /原文件。/
char path1[]=”/root/a11.txt”; /目录文件。/
char newpath[]=”/tmp/b.txt”; /一个不存在的文件。/
extern int errno; /错误号。/
if(rename(path,newpath)==0) /移动文件。/
{
printf(“the file %s was moved to %s .\n”,path,newpath); /移动成功。/
}
else
{
printf(“can’t move the file %s .\n”,path); /显示移动失败。/
printf("errno:%d\n",errno); /*显示错误号。*/
printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/
}
if(rename(path1,newpath)==0) /移动一个不存在的文件。/
{
printf(“the file %s was moved to %s .\n”,path1,newpath); /显示结果。/
}
else
{
printf(“can’t move the file %s .\n”,path1); /显示移动不成功能。/
printf("errno:%d\n",errno); /*显示错误号。*/
printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/
}
}
6.1.1.12 访问判断
有时我们需要判断文件是否可以进行某种操作(读,写等),这时可以使用access函数:
int access(const char*pathname,int mode)
pathname:文件名称
mode:要判断的访问权限。可以取以下值或者是他们的组合。R_OK:文件可读,W_OK:文件可写,X_OK:文件可执行,F_OK文件存在。
返回值:当我们测试成功时,函数返回0,否则如果一个条件不符时,返回-1。
例:
#include<unistd.h>
int main()
{
if (access(“/etc/passwd”,R_OK) ==0)
printf(“/etc/passwd can be read!\n”);
}
6.1.2 库函数-文件访问
6.1.2.1 创建和打开
FILE *fopen(const char *filename, const char *mode)
vfilename:打开的文件名(包含路径,缺省为当前路径)
vmode:打开模式
常见打开模式:
r, rb 只读方式打开
w, wb 只写方式打开,如果文件不存在,则创建该文件
a, ab 追加方式打开,如果文件不存 在,则创建该文件
r+, r+b, rb+ 读写方式打开
w+, w+b, wh+ 读写方式打开,如果文件不存在,则创建该文件
a+, a+b, ab+ 读和追加方式打开。如果文件不存在,则创建该文件
6.1.2.2 读
size_t fread(void *ptr, size_t size, size_t n, FILE *stream)
功能:从stream指向的文件中读取n个字段,每个字段为size字节,并将读取的数据放入ptr所指的字符数组中,返回实际已读取的字节数。
6.1.2.3 写
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream)
功能:从缓冲区ptr所指的数组中把n个字段写到stream指向的文件中,每个字段长为size个字节,返回实际写入的字段数。
6.1.2.4 库函数-读字符
int fgetc(FILE *stream) 从指定的文件中读一个字符
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen(“c1.txt”,”rt”))==NULL)
{
printf(“\nCannot open file strike any key exit! “);
getch();
exit(1);
}
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}
int getc(FILE *stream);
从指定输入流 stream 的当前位置读取一个字符,若读到文件尾而无数据时便返回EOF。
main()
{
int c;
FILE *fp= fopen(“d:\a.txt”,”r”);
fpos_t p=4;
fsetpos(fp,&p);
c=getc(fp);
putchar(c);
}
文件内容为:123456回车,输出:5
6.1.2.5 库函数-写字符
int fputc(int c, FILE *stream)
向指定的文件中写入一个字符
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen(“string”,”wt+”))==NULL) {
printf(“Cannot open file,strike any key exit!”);
getch();
exit(1);
}
printf(“input a string:\n”);
ch=getchar();
while (ch!=’\n’) {
fputc(ch,fp);
ch=getchar();
}
printf(“\n”);
fclose(fp);
}
6.1.2.6 库函数进行文件复制
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
FILE *from_fd;
FILE *to_fd;
long file_len=0;
char buffer[BUFFER_SIZE];
char *ptr;
/判断入参/
if(argc!=3)
{
printf(“Usage:%s fromfile tofile\n”,argv[0]);
exit(1);
}
/* 打开源文件 */
if((from_fd=fopen(argv[1],”rb”))==NULL)
{
printf(“Open %s Error\n”,argv[1]);
exit(1);
}
/* 创建目的文件 */
if((to_fd=fopen(argv[2],”wb”))==NULL)
{
printf(“Open %s Error\n”,argv[2]);
exit(1);
}
/测得文件大小/
fseek(from_fd,0L,SEEK_END);
file_len=ftell(from_fd);
fseek(from_fd,0L,SEEK_SET);
printf(“from file size is=%d\n”,file_len);
/进行文件拷贝/
while(!feof(from_fd))
{
fread(buffer,BUFFER_SIZE,1,from_fd);
if(BUFFER_SIZE>=file_len)
{
fwrite(buffer,file_len,1,to_fd);
}
else
{
fwrite(buffer,BUFFER_SIZE,1,to_fd);
file_len=file_len-BUFFER_SIZE;
}
bzero(buffer,BUFFER_SIZE);
}
fclose(from_fd);
fclose(to_fd);
exit(0);
}
6.1.2.7 库函数-格式化读
fscanf(FILE *stream, char *format[,argument…])
从一个流中进行格式化输入
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
int i;
printf(“Input an integer: “);
if(fscanf(stdin, “%d”, &i))
printf(“Theinteger read was: %i\n”, i);
return 0;
}
6.1.2.8 库函数-格式化写
int fprintf(FILE stream, char format[,argument,…])
格式化输出到一个流中
#include <stdio.h>
#include <process.h>
FILE *stream;
void main( void )
{
inti = 10;
double fp = 1.5;
char s[] = “this is a string”;
char c = ‘\n’;
stream = fopen( “fprintf.out”, “w” );
fprintf( stream, “%s%c”, s, c );
fprintf( stream, “%d\n”, i );
fprintf( stream, “%f\n”, fp );
fclose( stream ); }
6.1.2.9 库函数-定位
int fseek(FILE *stream, long offset, int whence)
whence:
SEEK_SET 从文件的开始处开始搜索
SEEK_CUR 从当前位置开始搜索
SEEK_END 从文件的结束处开始搜索
6.1.2.10 路径获取
在编写程序的时候,有时候需要得到当前路径。C库函数提供了getcwd来解决这个问题。
char getcwd(charbuffer,size_t size)我们提供一个size大小的buffer,getcwd会把当前的路径名copy 到buffer中.如果buffer太小,函数会返回-1。
#include<unistd.h>
main()
{
char buf[80];
getcwd(buf,sizeof(buf));
printf(“current working directory : %sn”,buf);
}
6.1.2.11 创建目录
#include <sys/stat.h>
int mkdir(char * dir, int mode)
功能:创建一个新目录。
返回值:0表示成功,-1表述出错。
6.1.2.12 linux下清空文件内容
1.truncate()函数
#include <unistd.h>
int truncate(const char *path,off_t length);
truncate()函数会将参数path指定的文件大小该为参数length指定的大小。如果原来的文件比length大,则删除超出的部分。所以,将length设为0,则清空原文件了。
2.用写文件方式打开文件,然后关闭,文件就已经被清除了。
6.1.2.13 ftell函数
函数 ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
6.1.2.14 bzero
原型:extern void bzero(void *s, int n);
参数说明:s 要置零的数据的起始地址; n 要置零的数据字节个数。
用法:#include <string.h>
功能:置字节字符串s的前n个字节为零且包括‘\0’。
说明:bzero无返回值,并且使用string.h头文件,string.h曾经是posix标准的一部分,但是在POSIX.1-2001标准里面,这些函数被标记为了遗留函数而不推荐使用。在POSIX.1-2008标准里已经没有这些函数了。推荐使用memset替代bzero。
6.1.3 IO处理模型
阻塞I/O模型
非阻塞模型
I/O多路转接模型
信号驱动I/O模型
异步I/O模型
6.1.4 多路复用
6.1.4.1 select()
int select(int numfds,fd_set *readfds, fd_set *writefds, fd_set *exeptfds,struct timeval *timeout)
numfds:要监视的文件描述符的最大值加1;
readfds:由select()监视的读文件描述符集合。
writefds:由select()监视的写文件描述符集合。
exeptfds:由select()监视的异常处理文件描述符集合。
timeout: null永远等待
0 从不等待。
函数返回值:大于0,成功,返回准备好的文件描述符的数目
0,超时
-1,出错
FD_ZERO(&set); /将set清零使集合中不含任何fd/
FD_SET(fd, &set); /将fd加入set集合/
FD_CLR(fd, &set); /将fd从set集合中清除/
FD_ISSET(fd, &set); /在调用select()函数后,用FD_ISSET来检测fd在fdset集合中的状态是否变化返回整型,当检测到fd状态发生变化时返回真,否则,返回假(0)/
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_setset);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位
6.1.4.2 pool()
int poll(struct pollfd *fds,int numfds,int timeout)
6.1.4.3 epool
epoll - I/O event notification facility
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
epoll的接口非常简单,一共就三个函数:
\1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
\2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
\3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
4、关于ET、LT两种工作模式:
可以得出这样的结论:ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.那么究竟如何来使用epoll呢?其实非常简单。
通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。
首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。
epoll_wait范围之后应该是一个循环,遍利所有的事件。
几乎所有的epoll程序都使用下面的框架:
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd) //有新的连接
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
}
else if( events[i].events&EPOLLIN ) //接收到数据,读socket
{
n = read(sockfd, line, MAXLINE)) < 0 //读
ev.data.ptr = md; //md为自定义类型,添加数据
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
}
else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //发送数据
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
}
Else
{
//其他的处理
}
}
}
6.1.5 struct stat结构体
可以得到文件属性
如:0==stat(pFileName,&sFileStat)可以用来判断文件是否存在
头文件:<sys/stat.h>
6.2 Linux时间编程
UTC Coordinated Universal Time 世界标准时间
GMT Greenwich Mean Time 格林威治标准时间
Calendar Time 日历时间,“从一个标准时间点到此时经过的秒数”
6.2.1 返回时间函数time
#include <time.h>
time_t time(time_t *tloc)
功能:获取日历时间,即从1970年1月1日0点到现在所经历的秒数。
/* typedeflong time_t */
time();这个函数其实保存的是一个历史时间,所以需要用NULL把这个历史时间清空一下,time()就会自动保存当前时间了。你可以简单的理解为NULL就是给time()初始化。
例:time1.c (演示)
#include<time.h>
#include <stdio.h>
int main(void){
struct tm *local;
time_t t;
t=time(NULL);
local=localtime(&t);
printf(“Local hour is: %d\n”,local->tm_hour);
local=gmtime(&t);
printf(“UTChour is: %d\n”,local->tm_hour); return 0;
}
6.2.2 当前时间函数gmtime
struct tm *gmtime(const time_t *timep)
功能:将日历时间转化为格林威治标准时间(英国零时区时间),并保存至TM结构。
6.2.3 本地时间函数localtime
struct tm *localtime(const time_t *timep)
功能:将日历时间转化为本地时间,并保存至TM结构。#include <stdio.h>
#include <time.h>**
main(){**
time_t t;***
struct tm *p;***
time(&t);***
p=localtime(&t);***
printf(“Year :%d\n”,1900+p->tm_year);***
printf(“Month :%d\n”,1+p->tm_mon);***
printf(“Day :%d\n”,p->tm_mday);***
printf(“Hour :%d\n”,p->tm_hour);***
printf(“Minute:%d\n”,p->tm_min);***
printf(“Second:%d\n”,p->tm_sec);***
printf(“Weekday:%d\n”,p->tm_wday);***
printf(“Days :%d\n”,p->tm_yday);***
printf(“Isdst :%d\n”,p->tm_isdst);***
getch();***
6.2.4 localtime_r
6.2.5 字符串格式时间函数ctime
char *ctime(time_t *timep);
将一个时间返回成一个可以识别的字符串格式,这里返回的时间已经转换成本地时间。#include <stdio.h>
#include <time.h>**
#include <stdlib.h>**
int main()**
{**
time_t *p;***
char s[30];***
time(p);***
strcpy(s,ctime(p)) ;***
printf(“%s\n”,s);***
getch();***
}**
6.2.6 字符串格式时间函数asctime
char *asctime(const struct tm *tm)
功能:将tm格式的时间转化为字符串,如:Sat Jul 30 08:43:03 2005
char *ctime(const time_t *timep)
功能:将日历时间转化为本地时间的字符串形式。
例:time2.c(演示)
#include <time.h>
#include <stdio.h>**
int main(void)**
{**
struct tm *ptr;**
time_tlt;**
lt=time(NULL);**
ptr=gmtime(<);**
printf(asctime(ptr));**
printf(ctime(<));**
return 0;**
}**
#include <stdio.h>**
#include <time.h>**
#include <stdlib.h>**
int main()**
{**
time_t *p;***
char s[30];***
struct gm *q;***
time(p);***
q=gmtime(p);***
strcpy(s,asctime(q)) ;***
printf(“%s\n”,s);***
}**
6.2.7 将时间转换成秒数函数mktime
time_t mktime(tm *timeptr);
将一个tm结构类型的时间转换成秒数时间,与gmtime的作用相反。
#include <stdio.h>**
#include <stdlib.h>**
#include <sys/time.h>**
#include <unistd.h>**
#include <time.h>**
main()**
*{ ***
time_t t;***
struct tm *p;***
char s[30];***
time(&t);***
strcpy(s,ctime(&t));***
printf(“%s\n”,s);***
p=gmtime(&t);***
t=mktime(p);***
time(&t);***
strcpy(s,ctime(&t));***
printf(“%s\n”,s);***
}**
Wed Dec 26 08:41:07 2007**
Wed Dec 26 08:41:07 2007**
6.2.8 取得当前的时间函数gettimeofday
前面所提到的时间函数只能把时间精确到秒,而此函数可以精确到微秒。
int gettimeofday(struct timeval tv,struct timezonetz)
功能:获取从今日凌晨到现在的时间差,常用于计算事件耗时。
struct timeval {**
int tv_sec;//秒数**
int tv_usec; //微妙数**
};**
timeval为时间**
timezone为时区**
#include<stdio.h>**
#include<sys/time.h>**
*int main(){ ***
struct timeval tv;**
gettimeofday(&tv,NULL);**
printf(“%d,%d\n”,tv.tv_sec,tv.tv_usec);**
return 0;**
}**
6.2.9 设置当前时间函数settimeofday
设置当前的系统时间。
int settimeofday(struct timeval *tv,struct timezone *tz);
#include <stdio.h>**
#include <sys/time.h>**
#include <unistd.h>**
#include <time.h>**
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.tv_sec=tv.tv_sec- 4000;***
settimeofday (&tv , &tz); ***
*} ***
6.2.10 tm结构
struct tm{
int tm_sec; //秒值
int tm_min; //分钟值
int tm_hour; //小时值
int tm_mday; //本月第几日
int tm_mon; //本年第几月
int tm_year; //tm_year + 1900 = 哪一年
int tm_wday; //本周第几日
int tm_yday; //本年第几日
int tm_isdst; //日光节约时间
};
6.2.11 时间显示
6.2.12 获取时间
6.2.13 计算程序执行耗时
*#include <sys/time.h> ***
#include <stdio.h>**
*#include <stdlib.h> ***
#include <math.h>**
/* 算法分析 /*
*void function() ***
*{ ***
**unsigned int i,j; ***
**double y; ***
**for(i=0;i<1000;i++) ***
** **for(j=0;j<1000;j++) ***
** ** **y++; ***
*} ***
*main() ***
*{ ***
**struct timeval tpstart,tpend; ***
**float timeuse; ***
gettimeofday(&tpstart,NULL); // 开始时间*
**function(); ***
gettimeofday(&tpend,NULL); // 结束时间*
/* 计算执行时间 /
**timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+ ***
** **tpend.tv_usec-tpstart.tv_usec; ***
**timeuse/=1000000; ***
**printf(“Used Time:%f\n”,timeuse); ***
**exit(0); ***
}**
6.2.14 延时执行
unsigned int sleep(unsignedint seconds)
功能:使程序睡眠seconds秒。
void usleep(unsignedlong usec)
功能:使程序睡眠usec微秒。
6.2.15 获取本地时间,以字符串显示
6.2.15.1 第一种方法
#include<time.h>**
#include <stdio.h>**
int main(void)**
{**
struct tm *local;*
char *asctime1,ctime1;
time_t t;*
t=time(NULL); ***
local=localtime(&t);*
asctime1=asctime(local);*
printf(asctime1);*
printf(“\n\n”);*
printf(asctime(local));*
ctime1=ctime(&t);*
printf(ctime1);*
printf(“\n\n”);*
return 0;*
}**
6.2.15.2 第二种方法
strftime
strftime() 函数根据区域设置格式化本地时间/日期,函数的功能将时间格式化,或者说格式化一个时间字符串。
#include
*#include <time.h> ***
using namespace std;**
int main()**
{**
*time_t t = time(0); ***
char tmp[64];**
*strftime( tmp, sizeof(tmp), “%Y-%m-%d %X”,localtime(&t) ); ***
*puts( tmp ); ***
system(“pause”);**
return 0;**
}**
程序运行结果:**
6.2.16 设置系统时间
#include <sys/time.h>**
#include <unistd.h>**
#include <stdio.h>**
#include <time.h>**
#include
using namespace std;**
*int main(void) ***
*{ ***
string temp= “2014-03-18 22:30:00”;***
**struct tm time_tm; ***
struct timeval time_tv; ***
time_t timep; ***
int ret = 0; ***
sscanf(temp.c_str(),”%d-%d-%d %d:%d:%d”, &time_tm.tm_year, &time_tm.tm_mon, &time_tm.tm_mday, &time_tm.tm_hour, &time_tm.tm_min, &time_tm.tm_sec); ***
time_tm.tm_year -= 1900; ***
time_tm.tm_mon -= 1; ***
time_tm.tm_wday = 0; ***
time_tm.tm_yday = 0; ***
time_tm.tm_isdst = 0; ***
timep = mktime(&time_tm); ***
time_tv.tv_sec = timep; ***
time_tv.tv_usec = 0; ***
ret = settimeofday(&time_tv, NULL); ***
if(ret != 0) ***
{ ***
fprintf(stderr, "settimeofday failed\n"); ***
return -1; ***
} ***
return 0; ***
*} ***
6.3 目录与文件
6.3.1 错误处理
将错误号转换成字符串,显示错误信息:strerror();它是一个指针
6.3.2 创建与删除目录
6.3.2.1 创建目录函数mkdir()
#include<sys/types.h>**
#include<sys/stat.h>**
int mkdir(char *pathname,mode_t mode);
pathname是一个字符型指针,表示需要创建的目录路径
mode表示权限的八进制数字。
错误信息:
#include <stdio.h>**
#include <sys/types.h>**
#include <sys/stat.h> ** ** ** ** ** ** /*包含头文件。/*
#include <errno.h>* ** ** ** ** ** ** /*包含头文件处理程序的错误。/**
main()**
*{ ***
extern int errno;** ** ** ** ** ** /*设置一个错误。/**
char *path=”/root/tmp11”;** ** /*定义一个字符串表示需要创建的目录。/**
if(mkdir(path,0766)==0)** ** ** /*创建一个目录。/**
** ** ** /*需要注意这里的权限设置参数,第一个0表示这里的是*
八进制数,766的含义如本章第一节所述。*/**
/\*如果目录创建成功,则会返回0,返回值与0进行比较。*/***
{***
** ** printf(“created the directory %s.\n”,path);** ** /*输出目录创建成功。/**
}***
else** ** ** ** ** ** ** ** ** ** /*如果不成功则输出提示信息。/**
{***
** ** printf(“cant’t creat the directory %s.\n”,path);** /*输出信息。/**
** ** printf(“errno:%d\n”,errno);** ** ** /*输出错误号。/**
** ** printf(“ERR :%s\n”,strerror(errno));/*输出错误信息。*/***
}***
}**
6.3.2.2 删除目录
int rmdir(char* pathname);**
在c++中应该添加另外两个头文件**
#include <unistd.h>//rmdir
#include* <string.h>//strerror*
#include <stdio.h>**
#include <sys/types.h>**
*#include <sys/stat.h> ***
#include <errno.h>* ** ** ** ** ** ** ** /*包含头文件。/**
main()**
*{ ***
extern int errno;** ** ** ** ** ** ** /*设置错误编号。/**
char *path=”/root/tmp11”;** ** ** ** ** /*设置需要删除的目录。/**
if(rmdir(path)==0)** ** ** ** ** ** ** /*删除文件,返回值与0比较。/**
{***
printf("deleted the directory %s.\n",path);** ** **/\*显示删除成功。*/***
}***
else** ** ** ** ** ** ** ** ** ** ** /*如果删除不成功。/**
{ ***
printf("cant't delete the directory %s.\n",path);** **/\*显示删除错误。*/***
printf("errno:%d\n",errno);** ** ** ** **/\*显示错误号。*/***
printf("ERR :%s\n",strerror(errno));** ** **/\*显示错误信息。*/***
}***
}**
6.3.3 创建文件
int creat(char *pathname,mode_t mode);
pathname 需要创建的文件名或目录名
mode文件权限
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h> /包含头文件。/
#include <unistd.h>//rmdir
#include <string.h>//strerror
int main ()
{
extern int errno; /定义错误号。/
const char *path=”./tmp.txt”;
if(creat(path,0766)==-1) /用creat函数创建一个文件。/
{
printf(“cant’t create the file %s.\n”,path); /不能创建文件。/
printf(“errno:%d\n”,errno); /显示错误号。/
printf(“ERR :%s\n”,strerror(errno)); /显示错误信息。/
}
else /另一种情况。/
{
printf(“created file %s.\n”,path); /显示创建成功。/
}
return 0 ;
}
6.3.4 删除文件
int remove(char *pathname);
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h> /包含头文件。/
#include <unistd.h>//rmdir
#include <string.h>//strerror
int main()
{
extern int errno; /定义一个错误号。/
const char *path=”./tmp.txt”; /定义要删除的文件名。/
if(remove(path)==0) /删除文件。/
{
printf(“Deleted file %s.\n”,path); /显示删除成功。/
}
else /另一种情况。/
{
printf(“cant’t delete the file %s.\n”,path);/显示删除失败。/
printf(“errno:%d\n”,errno); /显示错误号。/
printf(“ERR :%s\n”,strerror(errno)); /显示错误信息。/
}
return 0;
}
6.3.5 建立临时文件函数mkstemp()
int mkstemp(char *template);
参数template表示需要建立临时文件的文件名字符串。文件名字符串中最后6个字符必须是XXXXXX。mkstemp()函数会以可读写模式和0600权限来打开该文件,如果文件不存在,则会建立这个文件,返回值是打开文件的编号;如果文件建立不成功,则返回-1。
另外参数template字符串必须声明为数组。char template[]=”template-XXXXXX”;
不能使用char *template =”template-XXXXXX”;
6.3.6 文件打开关闭见前面
6.4 串口通信
数据通信方式:串行通信、并行通信
串行通信:利用一条传输线将数据以比特位为单位顺序传送。
并行通信:利用多条数据传输线将一个字数据的各比特位同时传送。
6.4.1 虚拟机虚拟串口设置
① 在关机情况下,设置
6.4.2 串口设置
options.c_cc[VTIME] = 10; //单位百毫秒
options.c_cc[VMIN] = 4;
/设置等待时间和最小接收字符/
new_cfg.c_cc[VTIME] = 50;
new_cfg.c_cc[VMIN] = 0;
6.5 Linux进程控制程序设计
进程分为运行态、就绪态、等待态。
进程号:Process Identity Number PID
父进程号:Parent Process Idenity Number PPID
6.5.1 系统调用函数列表
6.5.2 基本概念
6.5.2.1 进程
进程是一个具有一定独立功能的程序的一次运行活动。
6.5.2.2 临界资源
操作系统中将一次只允许一个程序访问的资源称为临界资源。
6.5.2.3 临界区
进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸程互斥地入各自的临界区。
6.5.2.4 进程同步
一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。
6.5.2.5 死锁
多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程都将永远不能再向前推进。
6.5.3 进程调度
按一定算法,从一组待运行的进程中选出一个来占有CPU运行。
6.5.3.1 调度方式:
• 抢占式
• 非抢占式
6.5.3.2 调度算法
l 先来先服务调度算法
l 短进程优先调度算法
l 高优先级优先调度算法
l 时间片轮转法
6.5.4 获取ID
头文件:
#include <sys/types.h>
#include <unistd.h>
获取本进程ID
vpid_t getpid(void)
获取父程ID
vpid_t getppid(void)
例:getpid.c(演示)
#include <stdio.h>**
#include <unistd.h>**
#include <stdlib.h>**
int main(void)**
{**
printf( “PID = %d\n”, getpid() );**
printf( “PPID = %d\n”, getppid() );**
return 0;**
}**
6.5.5 进程创建-fork
#include <unistd.h>
pid_t fork(void)
功能:创建子程
fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
\1. 在父程中,fork返回新创建的子程的PID;
\2. 在子程中,fork返回0;
\3. 如果出现错误,fork返回一个负值
例:fork1.c(演示)**
#include <sys/types.h>**
#include <unistd.h>**
main()**
{**
pid_t pid;**
/*此时仅有一个进程/***
pid=fork();**
/*此时已经有两个进程在同时运行/***
if(pid<0)**
printf(“errorin fork!”);**
elseif(pid==0)**
printf(“I am the child process, IDis %d\n”,getpid());**
else**
printf(“I am the parent process,IDis %d\n”,getpid());**
}**
在pid=fork()之前,只有一个进程在执行,但在这条语句执行之后,就变成两个进程在执行了,这两个进程的共享代码段,将要执行的下一条语句都是if(pid==0)。 两个程中,原来就存在的那个进程被称作“父程”,新出现的那个程被称作“子程”,父子进程的区别在于进程标识符(PID)不同。
6.5.6 进程创建-vfork
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void) 功能:创建子进程。
6.5.7 fork/ vfork区别:
\1. fork:子程拷贝父进程的数据段
vfork:子程与父程共享数据段
\2. fork:父、子进程的执行次序不确定
vfork:子程先运行,父程后运行
6.5.8 exec函数族
exec用被执行的程序替换调用它的程序。
区别:
fork创建一个新的进程,产生一个新的PID。
exec启动一个新程序,替换原有的程,因此进程的PID不会改变。
#include<unistd.h>
int execl(constchar* path,const char * arg1,….)
参数:path:被执行程序名(含完整路径)。
arg1 – argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
#include<unistd.h>
int execlp(const char * path,const char * arg1, …)
参数:
path:被执行程序名(不含路径,将从path环境变量中查找该程序)。
arg1 – argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
#include<unistd.h>
int execv(const char * path, char* const argv[ ])
参数:
path:被执行程序名(含完整路径)。
argv[]: 被执行程序所需的命令行参数数组。
例:execv.c (演示)
#include <unistd.h>
main()
{char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*)0};
execv(“/bin/ls”,argv);}
#include <stdlib.h>
int system(constchar*string)
功能:调用fork产生子程,由子进程来调用/bin/sh -c string来执行参数string所代表的命令。
6.5.9 进程等待
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *status)
功能:阻塞该进程,直到其某个子进程退出。
pid_t waitpid(pid_t pid,int *status,int options);
status: 子进程退出状态
例:wait.c(演示)**
#include <unistd.h>**
#include <sys/types.h>**
#include <sys/wait.h>**
#include <stdio.h>**
#include <stdlib.h>**
#include <errno.h>**
#include <math.h>**
int main(void)**
{**
pid_t child;*
/* 创建子进程 /
if((child=fork())==-1)*
{*
** printf(“Fork Error : %s\n”, strerror(errno));*
** exit(1);*
}*
**else ***
** if(child==0) // 子进程*
** {*
** ** printf(“the child process is run\n”);*
** ** sleep(1); //子进程睡眠一秒,但并没有去运行父进程*
** ** printf(“I am the child: %d\n”, getpid());*
** ** exit(0);*
** }*
** else //父进程*
** {*
** ** wait(NULL); //等到子进程退出,父进程才会运行*
** ** printf(“the father process is run\n”);*
** ** printf(“I am the father:%d\n”,getpid());*
** ** return 0;*
** }*
}* ***
6.5.10 进程终止
#include <stdlib.h>****
void exit(int status);****
6.5.11 守护进程
6.5.11.1 步骤
①创建子进程,父进程退出
pid=fork();**
if(pid>0)**
{**
exit(0);//父进程退出**
}**
②在子进程中创建新会话
#include<sys/types.h>*
#include<unistd.h>*
pid_t setsid(void);*
成功返回进程组id*
失败返回-1;**
③改变当前目录为根目录
④重设文件权限掩码
⑤关闭文件描述符
6.5.11.2 实例
6.5.11.3 守护进程的出错处理
6.6 Linux进程间通信程序设计
6.6.1 为什么进程间需要通信?
1、数据传输
一个进程需要将它的数据发送给另一个进程。
2、资源共享
多个进程之间共享同样的资源。
3、通知事件
一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
4、进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
POSIX(Portable Operating System Interface)表示可移植操作系统接口。电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 准,是为了提高 UNIX 环境下应用程序的可移植性。
6.6.2 Linux使用的进程间通信方式包括:
1、管道(pipe)和有名管道(FIFO)
2、信号(signal)
3、消息队列
4、共享内存
5、信号量
6、套接字(socket)
6.6.3 管道通信
什么是管道?
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。
6.6.3.1 管道创建
管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
无名管道由pipe()函数创建:
int pipe(int filedis[2]);
当一个管道建立时,它会创建两个文件描述符:filedis[0] 用于读管道, filedis[1] 用于写管道。
6.6.3.2 管道关闭
关闭管道只需将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
例**
#include <unistd.h>**
#include <errno.h>**
#include <stdio.h>**
#include <stdlib.h>**
int main()**
{**
int pipe_fd[2];**
if(pipe(pipe_fd)<0)**
{**
printf(“pipe create error\n”);**
return -1;**
}**
*else ***
printf(“pipe create success\n”);**
close(pipe_fd[0]);**
close(pipe_fd[1]);**
}**
6.6.3.3 管道读写
管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。
* *
必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。
例:管道读写pipe_rw.c
#include <unistd.h>**
#include <sys/types.h>**
#include <errno.h>**
#include <stdio.h>**
#include <string.h>**
#include <stdlib.h>**
int main()**
{**
int pipe_fd[2];*
pid_t pid;*
char buf_r[100];*
char* p_wbuf;*
int r_num;*
- memset(buf_r,0,sizeof(buf_r));*
/*创建管道/**
if(pipe(pipe_fd)<0)*
{*
** printf(“pipe create error\n”);*
** return -1;*
}*
/*创建子进程/**
if((pid=fork())==0) //子进程执行序列*
{*
** printf(“\n”);*
** close(pipe_fd[1]);//子进程先关闭了管道的写端*
** sleep(2); /*让父进程先运行,这样父进程先写子进程才有内容读/**
** if((r_num=read(pipe_fd[0],buf_r,100))>0)*
** {*
** ** printf(“%d numbers read from the pipe is %s\n”,r_num,buf_r);*
** } ***
** close(pipe_fd[0]);*
** exit(0);*
** }*
else if(pid>0) //父进程执行序列*
{*
** close(pipe_fd[0]); //父进程先关闭了管道的读端*
** if(write(pipe_fd[1],”Hello”,5)!=-1)*
** ** printf(“parent write1 Hello!\n”);*
** if(write(pipe_fd[1],” Pipe”,5)!=-1)*
** ** printf(“parent write2 Pipe!\n”);*
** close(pipe_fd[1]);*
** waitpid(pid,NULL,0); /*等待子进程结束/**
** exit(0);*
}*
return 0;*
}**
6.6.4 命名管道(FIFO)
命名管道和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。
6.6.4.1 创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode)
pathname:FIFO文件名
mode:属性(见文件操作章节)
一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO。
6.6.4.2 操作
当打开FIFO时,非阻塞标志(O_NONBLOCK)将对以后的读写产生如下影响:
1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞。
2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回,errno是ENXIO。
例:fifo_write.c
6.6.5 信号通信
信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:
1、当用户按某些按键时,产生信号。
2、硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号。
3、进程用kill函数将信号发送给另一个进程。
4、用户可用kill命令将信号发送给其他进程。
下面是几种常见的信号:
§ SIGHUP:从终端上发出的结束信号
§ SIGINT:来自键盘的中断信(Ctrl-C)
§ SIGKILL:该信号结束接收信号的进程
§ SIGTERM:kill 命令发出的信号
§ SIGCHLD:标识子进程停止或结束的信号
§ SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号
6.6.5.1 信号处理
1、忽略此信号
大多数信号都按照这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法。
2、执行用户希望的动作
通知内核在某种信发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。
3、执行系统默认动作
对大多数信号的系统默认动作是终止该进程。
6.6.5.2 信号发送
发送信号的主要函数有 kill和raise。
区别:Kill既可以向自身发送信号,也可以向其他进程发送信号。与kill函数不同的是,raise函数是向进程自身发送信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
int raise(int signo)
kill的pid参数有四种不同的情况:
1、pid>0
将信号发送给进程ID为pid的进程。
2、pid ==0
将信号发送给同组的进程。
3、pid < 0
将信号发送给其进程组ID等于pid绝对值的进程。
4、pid ==-1
将信号发送给所有进程。
6.6.5.3 Alarm
使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。
#include <unistd.h>
unsignedint alarm(unsignedint seconds)
Seconds:经过了指定的seconds秒后会产生信号SIGALRM。
每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。
6.6.5.4 pause
pause函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void)
只有执行了一个信号处理函数后,挂起才结束。
6.6.5.5 信号的处理
当系统捕捉到某个信时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。
信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。
6.6.5.5.1 signal
#include <signal.h>
void (*signal (int signo, void (*func)(int)))(int)
如何理解?
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_thandler))
Func可能的值是:
1、SIG_IGN:忽略此信号
2、SIG_DFL: 按系统默认方式处理
3、信号处理函数名:使用该函数处理
mysignal.c
#include <signal.h>**
#include <stdio.h>**
#include <stdlib.h>**
void my_func(int sign_no)**
{**
if(sign_no==SIGINT)*
** printf(“I have get SIGINT\n”);*
else if(sign_no==SIGQUIT)*
** printf(“I have get SIGQUIT\n”);*
}**
int main()**
{**
- printf(“Waiting for signal SIGINT or SIGQUIT \n “);*
/*注册信号处理函数/**
signal(SIGINT, my_func);*
signal(SIGQUIT, my_func);*
pause();*
exit(0);*
}**
6.6.6 共享内存
共享内存实现分为两个步骤:
一、创建共享内存,使用shmget函数。
二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
6.6.6.1 创建
int shmget ( key_t key, int size, int shmflg)
key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
返回值:如果成功,返回共享内存标识符;如果失败,返回-1。
6.6.6.2 映射
int shmat (int shmid, char *shmaddr, int flag)
参数:
shmid:shmget函数返回的共享存储标识符
flag:决定以什么方式来确定映射的地址(通常为0)
返回值:如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1。
当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。
intshmdt (char *shmaddr)
shmem.c
#include <stdlib.h>**
#include <stdio.h>**
#include <string.h>**
#include <errno.h>**
#include <unistd.h>**
#include <sys/stat.h>**
#include <sys/types.h>**
#include <sys/ipc.h>**
#include <sys/shm.h>**
#define PERM S_IRUSR|S_IWUSR**
/* 共享内存 /*
*int main(int argc,char **argv) ***
*{ ***
**int shmid; ***
**char *p_addr,*c_addr; ***
**if(argc!=2) ***
**{ ***
** **fprintf(stderr,”Usage:%s\n\a”,argv[0]); ***
** **exit(1); ***
}*
**/* 创建共享内存 /* ***
**if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1) ***
**{ ***
** **fprintf(stderr,”Create Share Memory Error:%s\n\a”,strerror(errno)); ***
** **exit(1); ***
**} ***
/* 创建子进程 /
if(fork()) // 父进程写*
**{ ***
** **p_addr=shmat(shmid,0,0); ***
** **memset(p_addr,’\0’,1024); ***
** strncpy(p_addr,argv[1],1024);*
** wait(NULL); // 释放资源,不关心终止状态*
** **exit(0); ***
**} ***
else // 子进程读*
**{ ***
** sleep(1); // 暂停1秒 ** ***
** **c_addr=shmat(shmid,0,0); ***
** **printf(“Client get %p\n”,c_addr); ***
** **exit(0); ***
**} ***
}**
6.6.7 消息队列
unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。消息队列(也叫做报文队列)则克服了这些缺点。
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。
6.6.7.1 键值
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj)
功能:返回文件名对应的键值。
pathname:文件名
proj:项目名(不为0即可)
6.6.7.2 打开/创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_tkey, int msgflg)
key:键值,由ftok获得。
msgflg:标志位。
返回值:与健值key相对应的消息队列描述字。
IPC_CREAT
创建新的消息队列
IPC_EXCL
与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
IPC_NOWAIT
读写消息队列要求无法得到满足时,不阻塞。
在以下两种情况下,将创建一个新的消息队列:
如果没有与健值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。
key参数为IPC_PRIVATE。
6.6.7.3 创建
int open_queue(key_t keyval)**
{**
int* qid;*
if((qid=msgget(keyval,IPC_CREAT))==-1)**
{**
return(-1);**
}**
return(qid);**
}**
6.6.7.4 发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg)
功能:向消息队列中发送一条消息。
msqid
已打开的消息队列id
msgp
存放消息的结构
msgsz
消息数据长度
msgflg
发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
消息格式
struct msgbuf
{
long mtype; /* 消息类型 > 0*/
char mtext[1]; /* 消息数据的首地址 */
};
6.6.7.5 接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long
msgtyp, int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。
int read_message(int qid,long type,struct mymsgbuf*qbuf)
{
int result,length;
length=sizeof(struct mymsgbuf)-sizeof(long);
if((result=msgrcv(qid,qbuf,length,type,0))==-1)
return(-1);
return(result);
}
6.6.8 信号量
信号量(又名:信号灯)与其他进程 间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
6.6.8.1 分类
二值信号灯:信号灯的值只能取0或1,类似于互斥锁。 但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
计数信号灯:信号灯的值可以取任意非负值。
6.6.8.2 创建/打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg)
key:键值,由ftok获得
nsems:指定打开或者新创建的信号灯集中将包含信号灯的数目
semflg:标识,同消息队列
6.6.8.3 操作
int semop(int semid, struct sembuf *sops, unsigned nsops)
功能:对信号量进行控制。
semid:信号量集的ID
sops:是一个操作数组,表明要进行什么操作
nsops:sops所指向的数组的元素个数。
struct sembuf{
unsigned short sem_num;/* semaphore
indexin array */
short sem_op;/* semaphore operation */
short sem_flg;/* operation flags */
};
sem_num:要操作的信号量在信号量集中的编号,第一个信号的编号是0。
sem_op:如果其值为正数,该值会加到现有的信号量值中,通常用于释放信号量;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值,通常用于获取信号量;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
Sem_flg:信号操作标志,可能的选择有两种:
IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
IPC_UNDO:程序结束时(不论正常或不正常)释放信量,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
6.6.8.4 Linux 进程间通信 — 信号通信
信号 ( signal ) 机制是 UNIX 系统中最为古老的进程间通信机制,很多条件可以产生一个信号.
信号的产生:****
1,当用户按下某些按键时,产生信号.
2,硬件异常产生信号:除数为 0 ,无效的存储访问等等.这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个 SIGSEGV 信号.
3,进程用 kill 函数 将信号发送给另一个进程.
4,用户可用 kill 命令将信号发送给其他进程.
信号类型 - SIGHUP SIGINT SIGKILL SIGTERM SIGCHLD SIGSTOP:****
下面是几种常见的信号:
SIGHUP :从终端上发出的结束信号.
SIGINT :来自键盘的中断信号 ( ctrl + c ) .
SIGKILL :该信号结束接收信号的进程 .
SIGTERM:kill 命令发出 的信号.
SIGCHLD:标识子进程停止或结束的信号.
SIGSTOP:来自键盘 ( **ctrl + z **) 或调试程序的停止执行信号.
6.6.8.4.1 信号处理:
当某信号出现时,将按照下列三种方式中的一种进行处理.
1,忽略此信号:大多数信号都按照这种方式进行处理,但有两种信号却决不能被忽略.它们是:SIGKILL 和 SIGSTOP . 这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法.****
2,执行用户希望的动作:通知内核在某种信号发生时,调用一个用户函数,在用户函数中,执行用户希望的处理.
3,执行系统默认动作:对大多数信号的系统默认动作是终止该进程.
当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式.
信号处理的主要方法有两种,一种是使用简单的 signal 函数,另一个是使用信号集函数.
6.6.8.4.2 信号发送 - kill 和 raise :
信号发送的主要函数有 kill 和 raise .
区别:kill 既可以向自身发送信号,也可以向其他进程发送信号,与 kill 函数不同的是,raise函数是向自身发送信号.
函数:
#include < sys/types.h >
#include < signal.h >
** int kill ( pid_t pai, int signo )**
** int raise ( int signo )**
kill 的 pid 参数有四种不同情况:****
1, pid > 0
将信号发送给进程 ID 为 pid 的进程.
2,pid = 0
将信号发送给同组的进程.
3,pid < 0
将信号发送给其进程组 ID 等于 pid 绝对值的进程.
4,pid = -1
将信号发送给所有进程.
6.6.8.4.3 Alarm信号闹钟 unsigned int alarm:
使用 alarm 函数可以设置一个时间值 ( 闹钟时间 ),当所设置的时间到了时,产生 SIGALRM 信号,如果不能扑捉此信号,则默认动作是终止该进程.
函数:** unsigned int alarm ( unsigned int seconds )**
经过了指定的 seconds 秒后会产生信号 SIGALRM.
每个进程只能有一个闹钟时间,如果在调用 alarm 时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前等级的闹钟时间则被新值替换.
如果有以前登记的尚未超时的闹钟时间,而这次 seconds 值是0 ,则表示取消以前的闹钟.
6.6.8.4.4 pause函数
int pause ( void ):
pause 函数使调用进程挂起直至捕捉到一个信号.
函数:** int pause ( void )**
只有执行了一个信号处理函数后,挂起才结束.
6.6.8.4.5 signal 函数
** void ( *signal ( int signo , void ( func ) ( int ) ) ) ( int ):*****
#include < signal.h >
void ( *signal ( int signo , void ( *func ) ( int ) ) ) ( int )
如何理解:
typedef void ( *sighandler_t ) ( int )
sighandler_t signal ( int signum , sighandler_t handler )
Func 可能的值是:
1,SIG_IGN :忽略此信号.
2,SIG_DFL :按照系统默认方式处理.
3,信号处理函数名:使用该函数处理.
**实例 — 小测试: ******
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUIT \n ");
/*注册信号处理函数*/
signal(SIGINT, my_func);
signal(SIGQUIT, my_func);
pause();
exit(0);
}
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf(“I have get SIGINT\n”);
else if(sign_no==SIGQUIT)
printf(“I have get SIGQUIT\n”);
}
int main()
{
printf(“Waiting for signal SIGINT or SIGQUIT \n “);
/注册信号处理函数/
signal(SIGINT, my_func);
signal(SIGQUIT, my_func);
pause();
exit(0);
}
测试方法:在终端下将该进程运行起来,然后 进程pause 了,我们再用 kill 给进程发送信号,
在另一终端下ps aux 可以找到运行进程的进程号.
然后kill -s SIGQUIT +进程号 我们可以在前一个终端下看到 I have get SIGQUIT.
SIGUSR1 :
kill -usr1 PID .
实例 — 按键驱动异步通知:****
驱动异步通知应用程序.
1,应用程序 注册信号处理函数 signal ( SIGIO , my_signal_fun ) :****
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd; //全局变量;
/*
应用程序 不会主动 的去读键值;
my_signal_fun 什么时候被调用呢?
在驱动程序的中断处理函数 static irqreturn_t buttons_irq(int irq, void *dev_id) 中,
有信号发送函数 kill_fasync (&button_async, SIGIO, POLL_IN) ;
当有按键按下时候,就会给应用程序发送一个信号;
这个信号就会触发 应用程序 调用信号处理函数 signal(SIGIO, my_signal_fun);
信号处理函数指向了 void my_signal_fun(int signum) 函数;
*/
void my_signal_fun(int signum)
{
unsigned **char** key_val;
read(fd, &key_val, 1); //读取键值;
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
**int** Oflags;
signal(SIGIO, my_signal_fun); /* 应用程序注册信号处理函数 */
// SIGIO 表示 Io 有数据可供读取;
fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
if (fd < 0)
{
printf("can't open!\n");
return -1;
}
/* 驱动程序发信号 */
fcntl(fd, F_SETOWN, getpid()); /* 信号发给谁,是通过这段程序告诉内核的 */
/* getpid() 获取应用程序PID */
/* 改变 Oflags 为异步通知 FASYNC*/
Oflags = fcntl(fd, F_GETFL); //读取 Oflags;
//改变 Oflags;
fcntl(fd, F_SETFL, Oflags | FASYNC); //这样,驱动程序里面的 .fasync = sixth_drv_fasync, 函数指针就会被调用;
// 改变 FASYNC标志, 最终调用到驱动的 fasync -> fasync_helper
//来初始化/或/释放 fasync_struct;
while (1)
{
sleep(1000);
}
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd; //全局变量;
/*
应用程序 不会主动 的去读键值;
my_signal_fun 什么时候被调用呢?
在驱动程序的中断处理函数 static irqreturn_t buttons_irq(int irq, void *dev_id) 中,
有信号发送函数 kill_fasync (&button_async, SIGIO, POLL_IN) ;
当有按键按下时候,就会给应用程序发送一个信号;
这个信号就会触发 应用程序 调用信号处理函数 signal(SIGIO, my_signal_fun);
信号处理函数指向了 void my_signal_fun(int signum) 函数;
*/
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1); //读取键值;
printf(“key_val: 0x%x\n”, key_val);
}
int main(int argc, char **argv)
{
int Oflags;
signal(SIGIO, my_signal_fun); /* 应用程序注册信号处理函数 */
// SIGIO 表示 Io 有数据可供读取;
fd = open(“/dev/buttons”, O_RDWR | O_NONBLOCK);
if (fd < 0)
{
printf(“can’t open!\n”);
return -1;
}
/* 驱动程序发信号 */
fcntl(fd, F_SETOWN, getpid()); /* 信号发给谁,是通过这段程序告诉内核的 */
/* getpid() 获取应用程序PID */
/* 改变 Oflags 为异步通知 FASYNC*/
Oflags = fcntl(fd, F_GETFL); //读取 Oflags;
//改变 Oflags;
fcntl(fd, F_SETFL, Oflags | FASYNC); //这样,驱动程序里面的 .fasync = sixth_drv_fasync, 函数指针就会被调用;
// 改变 FASYNC标志, 最终调用到驱动的 fasync -> fasync_helper
//来初始化/或/释放 fasync_struct;
while (1)
{
sleep(1000);
}
return 0;
}
2,谁发信号:驱动程序:
3,发给谁:应用程序 ; 应用程序要告诉驱动程序 其PID:
4,驱动程序怎么发信号:调用函数:kill_fasync :
在驱动程序开头 定义结构 fasync_struct :****
static struct fasync_struct *button_async;
static struct fasync_struct *button_async;
初始化 fasync_struct 结构体 使用 fasync_helper :****
在应用程序调用 .fasync = sixth_drv_fasync, 时候,调用 fasync_helper 函数 来初始化 struct fasync_struct *button_async 结构体;
struct fasync_struct *button_async 这个结构在 发信号的时候 kill_fasync (&button_async, SIGIO, POLL_IN) 能用的到;
[cpp] view plain copy print?
static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
printk("driver: sixth_drv_fasync\n");
return fasync_helper (fd, filp, on, &button_async); /* 初始化结构体 */
}
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = sixth_drv_open,
.read = sixth_drv_read,
.release = sixth_drv_close,
.poll = sixth_drv_poll,
.fasync = sixth_drv_fasync, /**************************** FASYNC ********************************/
};
static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
printk(“driver: sixth_drv_fasync\n”);
return fasync_helper (fd, filp, on, &button_async); /* 初始化结构体 */
}
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = sixth_drv_open,
.read = sixth_drv_read,
.release = sixth_drv_close,
.poll = sixth_drv_poll,
.fasync = sixth_drv_fasync, /**************************** FASYNC ********************************/
};
在按键驱动 服务程序 中发送信号 kill_fasync :****
kill_fasync 需要三个参数:
第一个:&button_async 包含进程 ID , 也就是 发给谁,
第二个: 发什么,发 SIGIO 这个信号,SIGIO 表示 Io 有数据可供读取;
第三个:POLL_IN , 原因, 表示有数据在等待读取;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned **int** pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
kill_fasync (&button_async, SIGIO, POLL_IN); /******** kill_fasync 发送信号 *********/
return IRQ_RETVAL(IRQ_HANDLED);
}
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
kill_fasync (&button_async, SIGIO, POLL_IN); /******** kill_fasync 发送信号 *********/
return IRQ_RETVAL(IRQ_HANDLED);
}
总结:****
为了使得设备支持异步通知机制,驱动程序最终涉及以下 3 项工作:
1,支持 F_SETOWN 命令 :****
能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID;
此项操作由内核完成,设备驱动无须处理;
2,支持 F_SETFL 命令的处理:****
当 FASYNC 标志改变的时候,驱动操作中的 fasync () 函数将得以执行;
驱动程序实现 fasync () 函数;
3,调用 kill_fasync () 函数:****
在设备资源可获得时, 调用 kill_fasync () 函数激发相应的信号.
6.6.9 多线程
6.6.9.1 优点
①线程适合多任务通信
同一进程中创建的多个线程功效相同的内存空间,不同的线程可以
②线程的创建和调度更高效
使用多线程的理由之一是:和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。
运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。
使用多线程的理由之二是:线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:
使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。
6.6.9.2 创建线程
#include <pthread.h>
int pthread_create(pthread_t * tidp,const pthread_attr_t *attr, void *(*start_rtn)(void),void *arg)
tidp:线程id
attr:线程属性(通常为空)
start_rtn:线程要执行的函数
arg:start_rtn的参数
例 thread_create.c
#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;***
/*创建线程1*/***
ret = pthread_create(&id1, NULL, (void*)myThread1, NULL);***
if (ret)***
{***
printf("Create pthread error!\n");***
return 1;***
}***
/*创建线程2*/***
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;***
}**
thread_share.c**
#include <stdio.h>**
#include <pthread.h>**
#include <unistd.h>**
//static int a=4;**
int a = 1;//数据段
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;***
int a=5;//栈 优先使用栈,后使用数据段。*
printf(“a = %d\n”,a);***
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;***
}**
6.6.9.3 编译
因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread
# gcc filename -lpthread
6.6.9.4 终止线程
如果进程中任何一个线程中调用exit或_exit,那么整个进程都会终止。线程的正常退出方式有:
(1) 线程从启动例程中返回
(2) 线程可以被另一个进程终止
(3) 线程自己调用pthread_exit函数
6.6.9.5 线程退出
#include <pthread.h>
void pthread_exit(void* rval_ptr)
功能:终止调用线程
Rval_ptr:线程退出返回值的指针。
thread_exit.c
#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);***
printf(“main thread!\n”);***
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;***
}**
6.6.9.6 线程等待
#include <pthread.h>
int pthread_join(pthread_t tid,void **rval_ptr)
功能:阻塞调用线程,直到指定的线程终止。
Tid:等待退出的线程id
Rval_ptr:线程退出的返回值的指针
thread_join.c
#include <pthread.h>**
#include <unistd.h>**
#include <stdio.h>**
void *thread(void str)*
{**
int i;***
for (i = 0; i < 10; ++i)***
{***
sleep(2);***
printf( "This in the thread : %d\n" , i );***
}***
return NULL;***
}**
int main()**
{**
pthread_t pth;***
int i;***
int ret = pthread_create(&pth, NULL, thread, (void *)(i));***
pthread_join(pth, NULL);***
printf(“123\n”);***
for (i = 0; i < 10; ++i)***
{***
//sleep(1);***
printf( "This in the main : %d\n" , i );***
}***
- return 0;***
}**
6.6.9.7 线程标识
#include <pthread.h>
pthread_t pthread_self(void)
功能:获取调用线程的 threadidentifier
thread_id.c
#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;***
}**
6.6.9.8 清除
线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。
从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *),void *arg)
功能:将清除函数压入清除栈
Rtn:清除函数
Arg:清除函数的参数
#include <pthread.h>
void pthread_cleanup_pop(int execute)
功能:将清除函数弹出清除栈
参数:Execute执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,非0:执行; 0:不执行
thread_clean.c
#include <stdio.h>**
#include <pthread.h>**
#include <unistd.h>**
void *clean(void arg)*
{**
printf(“cleanup :%s \n”,(char *)arg);***
return (void *)0;***
}**
void *thr_fn1(void arg)*
{**
printf(“thread 1 start \n”);***
pthread_cleanup_push( (void*)clean,”thread 1 first handler”);***
pthread_cleanup_push( (void*)clean,”thread 1 second hadler”);***
printf(“thread 1 push complete \n”);***
if(arg)***
{***
return((void \*)1);***
}***
pthread_cleanup_pop(0);***
pthread_cleanup_pop(0);***
return (void *)1;***
}**
void *thr_fn2(void arg)*
{**
printf(“thread 2 start \n”);***
pthread_cleanup_push( (void*)clean,”thread 2 first handler”);***
pthread_cleanup_push( (void*)clean,”thread 2 second handler”);***
printf(“thread 2 push complete \n”);***
if(arg)***
{***
pthread_exit((void \*)2);***
}***
pthread_cleanup_pop(0);***
pthread_cleanup_pop(0);***
pthread_exit((void *)2);***
}**
thread 2 start**
clean up: thread 2 first handler**
clean up: thread 2 second handler**
thread 2 push complete**
thread 2 start**
thread 2 push complete**
clean up: thread 2 first handler**
clean up: thread 2 second handler**
int main(void)**
{**
int err;***
pthread_t tid1,tid2;***
void *tret;***
err=pthread_create(&tid1,NULL,thr_fn1,(void *)1);***
if(err!=0)***
{***
printf("error .... \n");***
return -1;***
}***
err=pthread_create(&tid2,NULL,thr_fn2,(void *)1);***
if(err!=0)***
{***
printf("error .... \n");***
return -1;***
}***
err=pthread_join(tid1,&tret);***
if(err!=0)***
{***
printf("error .... \n");***
return -1;***
}***
printf(“thread 1 exit code %d \n”,(int)tret);***
err=pthread_join(tid2,&tret);***
if(err!=0)***
{***
printf("error .... ");***
return -1;***
}***
printf(“thread 2 exit code %d \n”,(int)tret);***
- return 1;***
}**
6.6.9.9 线程同步
进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决线程之间对资源的竞争:
1 互斥量Mutex
2 信号灯Semaphore
3 条件变量Conditions
6.6.10 互斥量
为什么需要互斥量:
Item *p =queue_list;
Queue_list=queue_list->next;
process_job(p);
free(p);
当线程1处理完Item *p=queue_list后,系统停止线程1的运行,改而运行线程2。线程2照样取出头节点,然后进行处理,最后释放了该节点。过了段时间,线程1重新得到运行。而这个时候,p所指向的节点已经被线程2释放掉,而线程1对此毫无知晓。他会接着运行process_job(p)。而这将导致无法预料的后果!
6.6.10.1 创建
在Linux中, 互斥量使用类型pthread_mutex_t表示。在使用前, 要对它进行初始化:
对于静态分配的互斥量, 可以把它设置为默认的mutex对象PTHREAD_MUTEX_INITIALIZER
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
int pthread_mutex_destroy(pthread_mutex_t *mutex)
6.6.10.2 加锁
对共享资源的访问, 要使用互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
返回值: 成功则返回0, 出错则返回错误编号。
trylock是非阻塞调用模式, 如果互斥量没被锁住, trylock函数将对互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。
6.6.10.3 解锁
在操作完成后,必须给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。
int pthread_mutex_unlock(pthread_mutex_t *mutex)
6.6.10.4 互斥量PK信号量
Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。
Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore。
Binary semaphore与Mutex的差异:
\1. mutex要由获得锁的线程来释放(谁获得,谁释放)。而semaphore可以由其它线程释放
\2. 初始状态可能不一样:mutex的初始值是1,而semaphore的初始值可能是0(或者为1)。
6.7 Linux网络应用程序设计
6.7.1 端口
端口是指计算机为了标识在计算机中访问网络的不同程序而设置的编号。
6.7.2 TCP/IP
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
6.7.2.1 网络模型
6.7.2.2 TCP/IP4层模型
网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收。数据帧是独立的网络信息传输单元。
网络层:负责将数据帧封装成IP数据包,并运行必要的路由算法。
传输层:负责端对端之间的通信会话连接与建立。
应用层:负责应用程序的网络访问,通过端口号来识别不同的进程。
6.7.2.3 TCP/IP协议族
TCP/IP 实际上一个协同工作的通信家族,为网络数据通信提供通路。为讨论方便可TCP/IP 协议组大体上分为三部分:
• Internet 协议(IP)
• 传输控制协议(TCP)和用户数据报协议(UDP)
• 处于 TCP 和 UDP 之上的一组应用协议。它们包括:TELNET,文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP)等。
TCP对话三次握手协议。
6.7.2.4 网络层
第一部分称为网络层。主要包括Internet 协议(IP)、网际控制报文协议(ICMP)和地址解析协议(ARP):
• Internet 协议(IP)该协议被设计成互联分组交换通信网,以形成一个网际通信环境。它负责在源主机和目的地主机之间传输来自其较高层软件的称为数据报文的数据块,它在源和目的地之间提供非连接型传递服务。
网际控制报文协议(ICMP)
它实际上不是IP层部分,但直接同IP层一起工作,报告网络上的某些出错情况。允许网际路由器传输差错信息或测试报文。
地址解析协议(ARP)
ARP 实际上不是网络层部分,它处于IP和数据链路层之间,它是在32位IP地址和48位物理地址之间执行翻译的协议。
6.7.2.5 传输层协议
第二部分是传输层协议,包括传输控制协议和用户数据报文协议。
• 传输控制协议(TCP):
该协议对建立网络上用户进程之间的对话负责,它确保进程之间的可靠通信,所提供的功能如下:
\1. 监听输入对话建立请求
\2. 请求另一网络站点对话
\3. 可靠的发送和接收数据
\4. 适度的关闭对话
• 用户数据报文协议(UDP):
UDP 提供不可靠的非连接型传输层服务,它允许在源和目的地之间传送数据,而不必在传送数据之前建立对话。它主要用于那些非连接型的应用程序,如:视频点播。
6.7.2.6 应用协议
这部分主要包括Telnet,文件传送协议(FTP 和TFTP),简单文件传送协议(SMTP)和域名服务(DNS)等协议。
6.7.2.7 IP协议
IP主要有以下四个主要功能:
• 数据传送
• 寻址
• 路由选择
• 数据报文的分段
IP的主要目的是为数据输入/输出网络提供基本算法,为高层协议提供无连接的传送服务。这意味着在IP将数据递交给接收站点以前不在传输站点和接收站点之间建立对话。它只是封装和传递数据,但不向发送者或接收者报告包的状态,不处理所遇到的故障。
IP包由IP协议头与协议数据两部分构成。
6.7.2.8 TCP协议
TCP是重要的传输层协议,目的是允许数据同网络上的其他节点进行可靠的交换。它能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输。
• TCP 协议具有严格的内装差错检验算法确保数据的完性。
• TCP 是面向字节的顺序协议,这意味着包内的每个字节被分配一个顺序编号,并分配给每包一个顺序编号。
6.7.2.9 UDP协议
UDP也是传输层协议,它是无连接的,不可靠的传输服务。当接收数据时它不向发送方提供确认信息,它不提供输入包的顺序,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文。由于它执行功能时具有较低的开销,因而执行速度比TCP快。
6.7.3 linux网络编程
socket,一种特殊的I/O接口,Linux中的网络编程通过Socket(套接字)接口实现,Socket是一种文件描述符。
6.7.3.1 套接字socket有三种类型:
l 流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
l 数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错,它使用数据报协议UDP。
l 原始套接字
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议的测试等。
6.7.3.2 地址结构
*struct sockaddr ***
*{ ***
*u_short sa_family; ***
*char sa_data[14]; ***
*} ***
Sa_family:地址族,采用“AF_xxx”的形式,
AF_INET:IPV4协议**
AF_INET6:IPV6协议*
AF_LOCAL:unix域协议*
AF_LINK:链路地址协议*
AF_KEY:密钥套接字*
Sa_data:14字节的特定协议地址。**
*struct sockaddr_in ***
{**
shortint sin_family; /* Internet地址族 /*
unsigned short int sin_port; /* 端口号 /*
struct in_addr sin_addr; /*IP地址 /*
unsigned char sin_zero[8]; /* 填0 /*
}**
编程中一般并不直接针对sockaddr数据结构操作,而是使用与sockaddr等价的sockaddr_in数据结构**
sin_family:**
AF_INET:IPV4协议**
AF_INET6:IPV6协议*
AF_LOCAL:unix域协议*
AF_LINK:链路地址协议*
AF_KEY:密钥套接字*
**struct in_addr ******
{****
unsigned long s_addr;****
}****
S_addr:32位的地址。****
IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
函数里面 a 代表 asciin 代表network.第一个函数表示将a.b.c.d形式的IP转换为32位的IP,存储在 inp指针里面。第二个是将32位IP转换为a.b.c.d的格式。
6.7.3.3 字节序转换
不同类型的 CPU 对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要统一的。所以当内部字节存储顺序和网络字节顺序不同时,就一定要进行转换。
32bit的整数(0x01234567)从地址0x100开始:
小端字节序:
大端字节序:
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用bigendian排序方式。
为什么要进行字节序转换?
例:INTEL的CPU使用的小端字节序MOTOROLA 68k系列CPU使用的是大端字节序MOTOROLA发一个16位数据0X1234给INTEL, 传到INTEL时 ,就被INTEL解释为0X3412 。
l htons 把unsigned short类型从主机序转换到网络序
l htonl 把unsignedlong类型从主机序转换到网络序
l ntohs 把unsigned short类型从网络序转换到主机序
l ntohl 把unsignedlong类型从网络序转换到主机序
6.7.3.4 如何判断大小端格式
int n=1
if(((char)&n)==1)小端
6.7.3.5 大小端概念
大小端(Big Endian vs Little Endian)
l <1>大端方式(MSDN中说的网络序):Motorola的PPC系列、IP协议中;(该方式和书写方式一致)
l <2>小端方式(MSDN中说的主机序):VAX计算机、Intel的x86系列;
字节内部的bit高低次序相同(左高右低),而字节之间的高低次序相反。
在32位系统中我们分以下这些情况来一一说明:
6.7.3.5.1 Byte类型(8bits)
在只有一个Byte的情况下,大端方式和小端方式没有分别。如:0x34
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | ** ** | |
---|---|---|---|---|---|---|---|---|---|
Bi**g Endian** | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0x34 |
** Little Endian** | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0x34 |
6.7.3.5.2 Short类型(16bits)
大小端方式之间有差别。如:0x1234
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | ** ** | |
---|---|---|---|---|---|---|---|---|---|
Bi**g Endian (0)** | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0x12 |
Bi**g Endian (1)** | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0x34 |
Little Endian (0) | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0x34 |
Little Endian (1) | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0x12 |
6.7.3.5.3 Long类型(32bits)
大小端方式之间有差别。如:0x12345678
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | ** ** | |
---|---|---|---|---|---|---|---|---|---|
Bi**g Endian (0)** | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0x12 |
Bi**g Endian (1)** | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0x34 |
Bi**g Endian (2)** | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 0x56 |
Bi**g Endian (3)** | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0x78 |
Little Endian (0) | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0x78 |
Little Endian (1) | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 0x56 |
Little Endian (2) | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0x34 |
Little Endian (3) | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0x12 |
6.7.3.5.4 位域的情况(边界对齐问题+大小端问题):
大小端方式之间有明显的差别。
在一个字节内,如3-4-1结构下的{4, 15, 0}
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | ** ** | |
---|---|---|---|---|---|---|---|---|---|
Bi**g Endian** | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0x9E |
Little Endian | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0x7C |
跨字节,边界不对齐,如5-4-7结构下的{2, 15, 0}
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | ** ** | |
---|---|---|---|---|---|---|---|---|---|
Bi**g Endian (0)** | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 0x17 |
Bi**g Endian (1)** | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0x80 |
Little Endian (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0x01 |
Little Endian (1) | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0xE2 |
6.7.3.6 IP与主机名
在网络上标识一台机器可以用IP,也可以使用主机名。
struct hostent *gethostbyname(const char *hostname)
struct hostent
{
char h_name; / 主机的正式名称 */
char h_aliases; / 主机的别名 */
int h_addrtype; /* 主机的地址类型 AF_INET*/
int h_length; /* 主机的地址长度 */
char *h_addr_list; / 主机的IP地址列表 */
}
#define h_addr h_addr_list[0] /* 主机的第一个IP地址*/
6.7.3.7 名字地址转换
#include<netdb.h>
struct hostent *gethostbyname(const char *hostname);
int getaddrinfo(const char *node,const char *service,const struct addrinfo *hints,struct addrinfo **result);
node:网络地址或者网络主机名
service:服务名或十进制的端口号字符串
hints:服务线索
result:返回结果
6.7.3.8 地址转换
IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
函数里面 a 代表 asciin 代表network.第一个函数表示将a.b.c.d形式的IP转换为32位的IP,存储在 inp指针里面。第二个是将32位IP转换为a.b.c.d的格式。
6.7.3.9 函数
进行Socket编程的常用函数有:
• socket 创建一个socket。
• bind 用于绑定IP地址和端口号到socket。
• connect 该函数用于绑定之后的client端,与服务器建立连接。
listen 设置能处理的最大连接要求,Listen()并未开始接收连线,只是设置socket为listen模式。
accept 用来接受socket连接。
send 发送数据
6.7.3.10 基于TCP-服务器
\1. 创建一个socket,用函数socket()
\2. 绑定IP地址、端口等信息到socket上,用函数bind()
\3. 设置允许的最大连接数,用函数listen()
\4. 接收客户端上来的连接,用函数accept()
\5. 收发数据,用函数send()和recv(),或者read()和write()
\6. 关闭网络连接
6.7.3.11 基于TCP-客户端
\1. 创建一个socket,用函数socket()
\2. 设置要连接的对方的IP地址和端口等属性
\3. 连接服务器,用函数connect()
\4. 收发数据,用函数send()和recv(),或者read()和write()
\5. 关闭网络连接
6.7.3.12 网络地址转换
6.7.3.12.1 网络字节顺序
每一台机器内部对变量的字节存储顺序不同,而网络传输的数据是一定要统一顺序的。所以对内部字节表示顺序与网络字节顺序不同的机器,一定要对数据进行转换,从程序的可移植性要求来讲,就算本机的内部字节表示顺序与网络字节顺序相同也应该在传输数据以前先调用数据转换函数,以便程序移植到其它机器上后能正确执行。真正转换还是不转换是由系统函数自己来决定的。
6.7.3.12.2 有关的转换函数
* unsigned short int htons(unsigned short int hostshort):
主机字节顺序转换成网络字节顺序,对无符号短型进行操作4bytes
* unsigned long int htonl(unsigned long int hostlong):
主机字节顺序转换成网络字节顺序,对无符号长型进行操作8bytes
* unsigned short int ntohs(unsigned short int netshort):
网络字节顺序转换成主机字节顺序,对无符号短型进行操作4bytes
* unsigned long int ntohl(unsigned long int netlong):
网络字节顺序转换成主机字节顺序,对无符号长型进行操作8bytes
注:以上函数原型定义在netinet/in.h里
6.7.3.12.3 inet_ntoa()
简述:
将网络地址转换成“.”点隔的字符串格式。
#include <winsock.h>
char FAR* PASCAL FAR inet_ntoa( struct in_addr in);
in:一个表示Internet主机地址的结构。
注释:
本函数将一个用in参数所表示的Internet地址结构转换成以“.” 间隔的诸如“a.b.c.d”的字符串形式。请注意inet_ntoa()返回的字符串存放在WINDOWS套接口实现所分配的内存中。应用程序不应假设该内存是如何分配的。在同一个线程的下一个WINDOWS套接口调用前,数据将保证是有效。
返回值:
若无错误发生,inet_ntoa()返回一个字符指针。否则的话,返回NULL。其中的数据应在下一个WINDOWS套接口调用前复制出来。
测试代码如下
include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int aargc, char* argv[])
{
struct in_addr addr1,addr2;
ulong l1,l2;
l1= inet_addr(“192.168.0.74”);
l2 = inet_addr(“211.100.21.179”);
memcpy(&addr1, &l1, 4);
memcpy(&addr2, &l2, 4);
printf(“%s : %sn”, inet_ntoa(addr1), inet_ntoa(addr2)); //注意这一句的运行结果
printf(“%sn”, inet_ntoa(addr1));
printf(“%sn”, inet_ntoa(addr2));
return 0;
}
实际运行结果如下:
192.168.0.74 : 192.168.0.74 //从这里可以看出,printf里的inet_ntoa只运行了一次。
192.168.0.74
211.100.21.179
inet_ntoa返回一个char *,而这个char *的空间是在inet_ntoa里面静态分配的,所以inet_ntoa后面的调用会覆盖上一次的调用。第一句printf的结果只能说明在printf里面的可变参数的求值是从右到左的,仅此而已。
6.7.3.13 各函数语法
6.7.3.13.1 socket
int socket(int family,int type,int protocol);
inet_addr()的功能是将一个点分十进制的IP转换成一个长整数型数(u_long类型)
inet_addr(“127.0.0.1”)
6.7.3.13.2 bind
int bind(int sockfd, struct sockaddr * my_addr, int addrlen);
返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中.
6.7.3.13.3 listen
#include<sys/socket.h>
int listen(int sockfd, int backlog)
返回:0──成功, -1──失败
6.7.3.13.4 accept
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
返回:非负描述字——成功, -1——失败
6.7.3.13.5 inet_pton
类似inet_addr
linux下:
#include <sys/socket.h>
#include <netinet/in.h>
#include<arpa/inet.h>
inet_pton函数原型:
inet_pton:将“点分十进制” -> “二进制整数”
int inet_pton(int af, const char *src, void *dst);
这个函数转换字符串到网络地址,第一个参数af是地址簇,第二个参数src是来源地址,第三个参数 dst接收转换后的数据。
inet_pton 是inet_addr的扩展,支持的多地址族有下列:
af = AF_INET
src为指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函数将该地址转换为in_addr的结构体,并复制在*dst中。
af = AF_INET6
src为指向IPV6的地址,函数将该地址转换为in6_addr的结构体,并复制在*dst中。
如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。
inet_pton例程
下面是例程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main (void)
{
char IPdotdec[20]; //存放点分十进制IP地址
struct in_addr s; // IPv4地址结构体
// 输入IP地址
printf(“Please input IP address: “);
scanf(“%s”, IPdotdec);
// 转换
inet_pton(AF_INET, IPdotdec, (void *)&s);
printf(“inet_pton: 0x%x\n”, s.s_addr); // 注意得到的字节序
// 反转换
inet_ntop(AF_INET, (void *)&s, IPdotdec, 16);
printf(“inet_ntop: %s\n”, IPdotdec);
}
6.7.3.13.6 setsockopt
设置套接口的选项。
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
optname:需设置的选项。
optlen:optval缓冲区长度。
6.7.3.13.7 sockaddr_ll结构体
6.7.3.13.8 Linux学习笔记之网络的send和recv函数
1、 函数原型
Int send(int socket, const void * buff, int length, int flags)
Int recv(int socket, void *buff, int length, int flags)
2、 对于正常的tcp通信来说,参数flags应设置为0
3、 Send()函数不会阻塞程序的执行(当套接字是阻塞的并且套接字的TCP发送缓冲区容不下应用程序缓冲区中的所有数据时,send()函数将会阻塞)。缓冲区的数据被发送到系统中的底层的TCP发送缓冲区,然后函数将返回。有可能在send()函数中定义的缓冲区中的数据不会全部被发送出去。Send()函数的返回值表示有多少个字节的数据已被发送到TCP发送缓冲区。确认这个返回值匹配缓冲区的大小以确保所有的数据都已被发送出去时非常重要的。
4、 Recv()函数是一个阻塞式函数。当文件描述符fd用fcntl()设置为非阻塞后,会理解返回-1,errno为resource temporarily unavailable ,或者可以用标志MSG_DONTWAIT来设置recv()当次读取数据为非阻塞,程序的执行将暂停直到函数从远程客户端接收到数据或者远程客户端明确的断开TCP会话。如果客户端断开TCP会话,recv()函数返回值为0。如果客户端发送数据包,recv()函数将接收到的数据放入定义的缓冲区并返回接收到的字节数。
Len = recv(fd, buff, len, 0)
Buff[len] = ‘\0’当接收到数据时,recv()函数返回的缓冲区长度被用于在缓冲区中放置一个NULL结束符。这确保了每个接收到的字符串都被正确的终止了。
while((n = recv(connfd, buff, sizeof(buff), 0)) > 0) //回发
{
buff[n] = ‘\0’;
printf(“recv : %s\n”,buff);
n = send(connfd, buff, n, 0);
if(n < 0)
{
perror(“write error\n”);
return -1;
}
buff[n] = ‘\0’;
printf(“write : %s\n”,buff);
}
当远程客户端关闭TCP会话连接时,recv()将返回0,用于退出while()循环。
5、 如果服务器和客户端同时等待在recv()函数调用上,它们将产生死锁,并且不会有通信发生。
6、 Send()函数发送的数据将被放入一个字节流中通过网络传递给接收主机,接收主机使用recv()函数读取字节流中的部分数据,但并没有办法控制读取字节流中哪一部分的数据,你不能保证通过一次recv()函数调用就可以读取由send()函数发送的所有数据。
7、 TCP中没有消息边界的概念。当消息使用send()函数并使用recv()函数接收时,它们不一定在发送和接收时使用相同的边界,因为TCP有处理数据的内部缓冲区。由于TCP数据包可能会发生重传,所以应用程序发送给套接字的数据会在LINUX系统内部缓存。
8、 由两次send()函数调用发送的数据可以被一次recv()函数调用接收。
9、 标记消息边界:i. 使用消息长度 ii. 使用消息结束标记。使用消息长度有两种方法:i. 每个消息的长度都相同 ii. 每个消息中都包含其长度的含义。结束标记是一个预先确定的字符或一组字符,它用于界定一个消息的结尾。当主机接收到每个数据包时,必须检查数据包中的每一个字节。如果它不是消息结束标记,就把它添加到临时缓冲区中,当发现消息结束标记时,临时缓冲区中的数据就被传输到永久缓冲区中以便使用,然后临时缓冲区被清空以用于重组下一个消息。
6.7.4 inet_pton
linux****下:
#include <sys/socket.h>
#include <netinet/in.h>
#include<arpa/inet.h>
inet_pton函数原型:
inet_pton:将“点分十进制” -> “二进制整数”
int inet_pton(int af, const char *src, void *dst);
这个函数转换字符串到网络地址,第一个参数af是地址簇,第二个参数src是来源地址,第三个参数 dst接收转换后的数据。
inet_pton 是inet_addr的扩展,支持的多地址族有下列:
af = AF_INET
src为指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函数将该地址转换为in_addr的结构体,并复制在*dst中。
af = AF_INET6
src为指向IPV6的地址,函数将该地址转换为in6_addr的结构体,并复制在*dst中。
如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。
inet_pton例程
下面是例程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main (void)
{
char IPdotdec[20]; //存放点分十进制IP地址
struct in_addr s; // IPv4地址结构体
// 输入IP地址
printf(“Please input IP address: “);
scanf(“%s”, IPdotdec);
// 转换
inet_pton(AF_INET, IPdotdec, (void *)&s);
printf(“inet_pton: 0x%x\n”, s.s_addr); // 注意得到的字节序
// 反转换
inet_ntop(AF_INET, (void *)&s, IPdotdec, 16);
printf(“inet_ntop: %s\n”, IPdotdec);
}
6.8 其他系统函数
6.8.1 字符串处理
6.8.1.1 char *strcasestr(const char *haystack,const char *needle)
在haystack中查找needle第一次出现的位置的char指针
6.8.1.2 char *strtok(char *str,const char *delimiters)
str:待分割字符串
delimiters:分割符
当strtok()在str中发现delimiters则将delimiters改为’\0’
注意:第一次调用strtok时必须给出str,第二次调用时将str设置成NULL,每次调用成功将会返回分割出片段的指针。
ptr = strtok(str,’,’);
if(ptr!=NULL)
{
ptr=strtok(NULL,’,’);
}
strtok_s Windows下的分割字符串函数
char *strtok_s(char *str,const char *strDelimit, char **buf)
函数将剩下的存于buf中
strtok_r Linux下的分割字符串函数
char *strtok_r(char *str,const char *delimit, char **saveptr)
6.8.2 getrlimit()与setrlimit()函数详解
功能描述:
获取或设定资源使用限制。每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。非授权调用进程只可以将其软限制指定为0~硬限制范围中的某个值,同时能不可逆转地降低其硬限制。授权进程可以任意改变其软硬限制。RLIM_INFINITY的值表示不对资源限制。
用法:
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
参数:
resource:可能的选择有
RLIMIT_AS //进程的最大虚内存空间,字节为单位。
RLIMIT_CORE //内核转存文件的最大长度。
RLIMIT_CPU //最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。然而,可以捕捉信号,处理句柄可将控制返回给主程序。如果进程继续耗费CPU时间,核心会以每秒一次的频率给其发送SIGXCPU信号,直到达到硬限制,那时将给进程发送 SIGKILL信号终止其执行。
RLIMIT_DATA //进程数据段的最大值。
RLIMIT_FSIZE //进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
RLIMIT_LOCKS //进程可建立的锁和租赁的最大值。
RLIMIT_MEMLOCK //进程可锁定在内存中的最大数据量,字节为单位。
RLIMIT_MSGQUEUE //进程可为POSIX消息队列分配的最大字节数。
RLIMIT_NICE //进程可通过setpriority() 或 nice()调用设置的最大完美值。
RLIMIT_NOFILE //指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
RLIMIT_NPROC //用户可拥有的最大进程数。
RLIMIT_RTPRIO //进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。
RLIMIT_SIGPENDING //用户可拥有的最大挂起信号数。
RLIMIT_STACK //最大的进程堆栈,以字节为单位。
rlim:描述资源软硬限制的结构体,原型如下
struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};
返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EFAULT:rlim指针指向的空间不可访问
EINVAL:参数无效
EPERM:增加资源限制值时,权能不允许
延伸阅读:
ulimit和setrlimit轻松修改task进程资源上限值
在linux系统中,Resouce limit指在一个进程的执行过程中,它所能得到的资源的限制,比如进程的core file的最大值,虚拟内存的最大值等。
Resouce limit的大小可以直接影响进程的执行状况。其有两个最重要的概念:soft limit 和 hard limit。
struct rlimit {
rlim_t rlim_cur; //soft limit
rlim_t rlim_max; //hard limit
};
soft limit是指内核所能支持的资源上限。比如对于RLIMIT_NOFILE(一个进程能打开的最大文件数,内核默认是1024),soft limit最大也只能达到1024。对于RLIMIT_CORE(core文件的大小,内核不做限制),soft limit最大能是unlimited。
hard limit在资源中只是作为soft limit的上限。当你设置hard limit后,你以后设置的soft limit只能小于hard limit。要说明的是,hard limit只针对非特权进程,也就是进程的有效用户ID(effective user ID)不是0的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE),soft limit则只有内核上限。
我们可以来看一下下面两条命令的输出。
sishen@sishen:$ ulimit -c -n -s$ ulimit -c -n -s -H
core file size (blocks, -c) 0
open files (-n) 1024
stack size (kbytes, -s) 8192
sishen@sishen:
core file size (blocks, -c) unlimited
open files (-n) 1024
stack size (kbytes, -s) unlimited
-H表示显示的是hard limit。从结果上可以看出soft limit和hard limit的区别。unlimited表示no limit, 即内核的最大值。
对于resouce limit的读取修改,有两种方法。
* 使用shell内建命令ulimit
* 使用getrlimit和setrlimit API
ulimit是改变shell的resouce limit,并达到改变shell启动的进程的resouce limit效果(子进程继承)。
usage:ulimit [-SHacdefilmnpqrstuvx [limit]]
当不指定limit的时候,该命令显示当前值。这里要注意的是,当你要修改limit的时候,如果不指定-S或者-H,默认是同时设置soft limit和hard limit。也就是之后设置时只能减不能增。所以,建议使用ulimit设置limit参数是加上-S。
getrlimit和setrlimit的使用也很简单,manpage里有很清楚的描述。
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
需要注意的是你在setrlimit,需要检查是否成功来判断新值有没有超过hard limit。如下例Linux系统中在应用程序运行过程中经常会遇到程序突然崩溃,提示:Segmentation fault,这是因为应用程序收到了SIGSEGV信号。这个信号提示当进程发生了无效的存储访问,当接收到这个信号时,缺省动作是:终止w/core。终止w/core的含义是:在进程当前目录生成core文件,并将进程的内存映象复制到core文件中,core文件的默认名称就是“core”(这是 Unix类系统的一个由来已久的功能)。
事实上,并不是只有SIGSEGV信号产生coredump,还有下面一些信号也产生coredump:SIGABRT(异常终止)、SIGBUS(硬件故障)、SIGEMT(硬件故障)、SIGFPE(算术异常)、SIGILL(非法硬件指令)、SIGIOT(硬件故障),SIGQUIT,SIGSYS(无效系统调用),SIGTRAP(硬件故障)等。Linux系统中在应用程序运行过程中经常会遇到程序突然崩溃,提示:Segmentation fault,这是因为应用程序收到了SIGSEGV信号。这个信号提示当进程发生了无效的存储访问,当接收到这个信号时,缺省动作是:终止w/core。终止w/core的含义是:在进程当前目录生成core文件,并将进程的内存映象复制到core文件中,core文件的默认名称就是“core”(这是 Unix类系统的一个由来已久的功能)。
事实上,并不是只有SIGSEGV信号产生coredump,还有下面一些信号也产生coredump:SIGABRT(异常终止)、SIGBUS(硬件故障)、SIGEMT(硬件故障)、SIGFPE(算术异常)、SIGILL(非法硬件指令)、SIGIOT(硬件故障),SIGQUIT,SIGSYS(无效系统调用),SIGTRAP(硬件故障)等。对于resouce limit的读取修改,有两种方法。
* 使用shell内建命令ulimit
* 使用getrlimit和setrlimit APIsetrlimit:
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_CORE, &rlim_new)!=0) {
rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
(void) setrlimit(RLIMIT_CORE, &rlim_new);
}
}
6.8.3 signal()函数
在<signal.h> 这个头文件中。
signal(参数1,参数2);
参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号时系统定义的宏。
参数2:我们处理的方式(是系统默认还是忽略还是捕获)。
一般有3中方式进行操作。
(1)eg: signal(SIGINT ,SIG_ING );
//SIG_ING 代表忽略SIGINT信号,SIGINT信号代表由InterruptKey产生,通常是CTRL +C 或者是DELETE 。发送给所有ForeGround Group的进程。
下面我们写个死循环:
这时我们保存执行。
按下CTRL _C程序没有反应。这就对了
如果我们想结束该程序可以按下CTRL +\来结束
其实当我们按下CTRL +\组合键时,是产生了SIGQUIT信号
(2)eg: signal(SIGINT ,SIG_DFL );
//SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作时终止该进程。这与不写此处理函数是一样的。
我们将上面的程序改成
这时就可以按下CTRL +C 来终止该进程了。把signal(SIGINT,SIG_DFL);这句去掉,效果是一样的。
(3) void ( signal( int sig, void ( handler)( int )))( int );
int (*p)();
这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.
int (*fun())();
这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.
void (*signal(int signo, void (*handler)(int)))(int);就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数),而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.
在写信号处理函数时对于信号处理的函数也是void sig_fun(int signo);这种类型,恰好与上面signal()函数所返回的函数指针所指向的函数是一样的.void ( *signal() )( int );
signal是一个函数, 它返回一个函数指针, 后者所指向的函数接受一个整型参数 且没有返回值, 仔细看, 是不是siganal( int signo, void (*handler)(int) )的第2个参数了,对了,其实他所返回的就是 signal的第2个信号处理函数,指向信号处理函数,就可以执行函数了( signal内部时, signal把信号做为参数传递给handler信号处理函数,接着 signal函数返回指针, 并且又指向信号处理函数, 就开始执行它)
那么,signal函数的参数又是如何呢?signal函数接受两个参数:一个整型的信号编号,以及一个指向用户定义的信号处理函数的指针。我们此前已经定义了指向用户定义的信号处理函数的指针sfp:
sfp 的类型可以通过将上面的声明中的sfp去掉而得到,即void (*)(int)。此外,signal函数的返回值是一个指向调用前的用户定义信号处理函数的指针,这个指针的类型与sfp指针类型一致。因此,我们可以如下声明signal函数:
同样地,使用typedef可以简化上面的函数声明:
Ok;看个例子:
此程序是对当我们按下CTRL +C键时,会执行我们定义的信号处理函数。
每当我们按下CTRL +C键时会打印该信号的number.可以看出该信号的num为2
要想退出可以按下CTRL +\ 打印结果为最后一行。
一些常用的Signal 如下:
注:下面是从百度文库中找的(^__^) 嘻嘻……
Signal | Description |
---|---|
SIGABRT | 由调用abort函数产生,进程非正常退出 |
SIGALRM | 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
SIGBUS | 某种特定的硬件异常,通常由内存访问引起 |
SIGCANCEL | 由Solaris Thread Library内部使用,通常不会使用 |
SIGCHLD | 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略 |
SIGCONT | 当被stop的进程恢复运行的时候,自动发送 |
SIGEMT | 和实现相关的硬件异常 |
SIGFPE | 数学相关的异常,如被0除,浮点溢出,等等 |
SIGFREEZE | Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP | 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送 |
SIGILL | 非法指令异常 |
SIGINFO | BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程 |
SIGINT | 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程 |
SIGIO | 异步IO事件 |
SIGIOT | 实现相关的硬件异常,一般对应SIGABRT |
SIGKILL | 无法处理和忽略。中止某个进程 |
SIGLWP | 由Solaris Thread Libray内部使用 |
SIGPIPE | 在reader中止之后写Pipe的时候发送 |
SIGPOLL | 当某个事件发送给Pollable Device的时候发送 |
SIGPROF | Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR | 和系统相关。和UPS相关。 |
SIGQUIT | 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程 |
SIGSEGV | 非法内存访问 |
SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
SIGSTOP | 中止进程。无法处理和忽略。 |
SIGSYS | 非法系统调用 |
SIGTERM | 请求中止进程,kill命令缺省发送 |
SIGTHAW | Solaris专用,从Suspend恢复时候发送 |
SIGTRAP | 实现相关的硬件异常。一般是调试异常 |
SIGTSTP | Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN | 当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU | 当Background Group的进程尝试写Terminal的时候发送 |
SIGURG | 当out-of-band data接收的时候可能发送 |
SIGUSR1 | 用户自定义signal 1 |
SIGUSR2 | 用户自定义signal 2 |
SIGVTALRM | setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING | Solaris Thread Library内部实现专用 |
SIGWINCH | 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU | 当CPU时间限制超时的时候 |
SIGXFSZ | 进程超过文件大小限制 |
SIGXRES | Solaris专用,进程超过资源限制的时候发送 |
6.8.4 WIFEXITED/WEXITSTATUS/WIFSIGNALED
If the exit status value (*note Program Termination::) of the child
process is zero, then the status value reported by waitpid' or
wait’
is also zero. You can test for other kinds of information encoded in
the returned status value using the following macros. These macros are
defined in the header file `sys/wait.h’.
– Macro: int WIFEXITED (int STATUS)
This macro returns a nonzero value if the child process terminated
normally with exit' or
_exit’.
– Macro: int WEXITSTATUS (int STATUS)
If `WIFEXITED’ is true of STATUS, this macro returns the low-order
8 bits of the exit status value from the child process. *Note
Exit Status::.
– Macro: int WIFSIGNALED (int STATUS)
This macro returns a nonzero value if the child process terminated
because it received a signal that was not handled. *Note Signal
Handling::.
子进程的结束状态返回后存于status,底下有几个宏可判别结束情况
WIFEXITED(status)如果子进程正常结束则为非0值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status)取得引发子进程暂停的信号代码
6.8.5 Linux下send、sendto、sendmsg函数分析
从网上查到了一些关于这几个函数的使用及注意事项,现终结如下:
功能描述:
发送消息,send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。sendto 和 sendmsg既可用于无连接的套接字,也可用于基于连接的套接字。除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。
参数:
sock:索引将要从其发送数据的套接字。
buf:指向将要发送数据的缓冲区。
len:以上缓冲区的长度。
flags:是以下零个或者多个标志的组合体,可通过or操作连在一起
MSG_DONTROUTE:不要使用网关来发送封包,只发送到直接联网的主机。这个标志主要用于诊断或者路由程序。
MSG_DONTWAIT:操作不会被阻塞。
MSG_EOR:终止一个记录。
MSG_MORE:调用者有更多的数据需要发送。
MSG_NOSIGNAL:当另一端终止连接时,请求在基于流的错误套接字上不要发送SIGPIPE信号。
MSG_OOB:发送out-of-band数据(需要优先处理的数据),同时现行协议必须支持此种操作。
to:指向存放接收端地址的区域,可以为NULL。
tolen:以上内存区的长度,可以为0。
msg:指向存放发送消息头的内存缓冲,结构形态如下
struct msghdr {
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
size_t msg_iovlen;
void *msg_control;
socklen_t msg_controllen;
int msg_flags;
};
可能用到的数据结构有
struct cmsghdr {
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
};
返回说明:
成功执行时,返回已发送的字节数。失败返回-1,errno被设为以下的某个值
EACCES:对于Unix域套接字,不允许对目标套接字文件进行写,或者路径前驱的一个目录节点不可搜索
EAGAIN,EWOULDBLOCK: 套接字已标记为非阻塞,而发送操作被阻塞
EBADF:sock不是有效的描述词
ECONNRESET:连接被用户重置
EDESTADDRREQ:套接字不处于连接模式,没有指定对端地址
EFAULT:内存空间访问出错
EINTR:操作被信号中断
EINVAL:参数无效
EISCONN:基于连接的套接字已被连接上,同时指定接收对象
EMSGSIZE:消息太大
ENOMEM:内存不足
ENOTCONN:套接字尚未连接,目标没有给出
ENOTSOCK:sock索引的不是套接字
EPIPE:本地连接已关闭
6.8.6 Makecontext/getcontext/swapcontext实现用户线程
6.8.6.1 ucontext_t
typedef struct ucontext //用户级线程上下文
{
unsigned long int uc_flags;
struct ucontext *uc_link; //保存线程退出时返回的上下文
stack_t uc_stack; //线程栈
mcontext_t uc_mcontext;
__sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
} ucontext_t;
typedef struct sigaltstack {
void *ss_sp; //栈顶指针
int ss_flags;
size_t ss_size; //栈大小
} stack_t;
6.8.6.2 Ucontext 1
现代Unix系统都在ucontext.h中提供用于上下文切换的函数,这些函数有getcontext, setcontext,swapcontext 和makecontext。
getcontext用于保存当前上下文;
setcontext用于切换上下文;
swapcontext会保存当前上下文并切换到另一个上下文;
makecontext创建一个新的上下文。
用户级线程的抢占
6.8.6.3 ucontext机制2
所谓 “ucontext” 机制是 GNU C 库提供的一组用于创建、保存、切换用户态执行“上下文”(context)的API,可以看作是 “setjmp/long_jmp” 的“升级版”,主要包括以下四个函数:
void makecontext(ucontext_t *ucp, void (*func)(), int argc, …);
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);
结构体 ucontext_t 和上述4个函数声明一起定义在系统头文件<ucontext.h> 中,该类型的具体实现与体系结构相关,但规范要求其至少要包含以下字段:
typedef struct ucontext {
struct ucontext *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
…
} ucontext_t;
其中 sigset_t 和 stack_t 定义在标准头文件 <signal.h> 中, uc_link 字段保存当前context结束后继续执行的context记录, uc_sigmask 记录该context运行阶段需要屏蔽的信号,uc_stack 是该context运行的栈信息, 最后一个字段uc_mcontext 则保存具体的程序执行上下文——如PC值、堆栈指针、寄存器值等信息——其实现方式依赖于底层运行的系统架构,是平台、硬件相关的。
下面具体来看每个函数的功能:
• int makecontext(ucontext_t *ucp, void (*func)(), int argc, …) 该函数用以初始化一个ucontext_t类型的结构,也就是我们所说的用户执行上下文。函数指针func指明了该context的入口函数,argc指明入口参数个数,该值是可变的,但每个参数类型都是int型,这些参数紧随argc传入。 另外,在调用makecontext之前,一般还需要显式的指明其初始栈信息(栈指针SP及栈大小)和运行时的信号屏蔽掩码(signal mask)。 同时也可以指定uc_link字段,这样在func函数返回后,就会切换到uc_link指向的context继续执行。
• int setcontext(const ucontext_t *ucp) 该函数用来将当前程序执行线索切换到参数ucp所指向的上下文状态,在执行正确的情况下,该函数直接切入到新的执行状态,不再会返回。比如我们用上面介绍的makecontext初始化了一个新的上下文,并将入口指向某函数entry(),那么setcontext成功后就会马上运行entry()函数。
• int getcontext(ucontext_t *ucp) 该函数用来将当前执行状态上下文保存到一个ucontext_t结构中,若后续调用setcontext或swapcontext恢复该状态,则程序会沿着getcontext调用点之后继续执行,看起来好像刚从getcontext函数返回一样。 这个操作的功能和setjmp所起的作用类似,都是保存执行状态以便后续恢复执行,但需要重点指出的是:getcontext函数的返回值仅能表示本次操作是否执行正确,而不能用来区分是直接从getcontext操作返回,还是由于setcontext/swapcontex恢复状态导致的返回,这点与setjmp是不一样的。
• int swapcontext(ucontext_t *oucp, ucontext_t *ucp) 理论上,有了上面的3个函数,就可以满足需要了(后面讲的libgo就只用了这3个函数,而实际只需setcontext/getcontext就足矣了),但由于getcontext不能区分返回状态,因此编写上下文切换的代码时就需要保存额外的信息来进行判断,显得比较麻烦。 为了简化切换操作的实现,ucontext 机制里提供了swapcontext这个函数,用来“原子”地完成旧状态的保存和切换到新状态的工作(当然,这并非真正的原子操作,在多线程情况下也会引入一些调度方面的问题,后面会详细介绍)。 为了进一步理解swapcontext这个函数的设计目的,可以尝试利用getcontext/setcontext完成同样的功能,你需要怎样编写代码? 同时,也不妨思考一下下面这段代码的执行结果(该例出自维基百科Setcontext 条目):
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
ucontext_t context;
getcontext(&context);
puts(“Hello world”);
sleep(1);
setcontext(&context);
return 0;
}
可以看出,用ucontext机制实现一个“协程”系统并不困难。 实际上,每个运行上下文(ucontext_t)就直接对应于“协程”概念,对于协程的“创建”(Create)、“启动” (Spawn)、“挂起” (Suspend)、“切换” (Swap)等操作,很容易通过上面的4个API及其组合加以实现,需要的工作仅在于设计一组数据结构保存暂不运行的context结构,提供一些调度的策略即可。 这方面的开源实现有很多,其中最著名的就是Go的前身,libtask库。
对于将“协程”映射到多OS线程执行的情形,就要稍稍复杂一些,但主要的问题是集中在共享任务队列的实现、调度线程间的互斥等,至于“协程”的映射问题,与单线程情况没有太大的区别。 对于这方面的开源借鉴,当然首推Go的运行时 —— 但由于标准Go实现没有使用GNU C库,而是自行设计了包括C编译器在内的整套工具链,因而就没有直接采用ucontext机制(尽管其内部实现机制与ucontext原理类似)。
在下一篇中,会转而分析Go语言前端的运行时实现——libgo。libgo的调度器部分基本用C开发并由GCC编译,“goroutine”(Go语言中相对于“协程”的概念)也直接以“ucontext”机制实现,其代码对于分析C语言下“协程”系统实现方法而言,具有较高的参考价值。
6.8.6.4 Ucontext协程理解3
1.干货写在前面
协程是一种用户态的轻量级线程。本篇主要研究协程的C/C++的实现。
首先我们可以看看有哪些语言已经具备协程语义:
比较重量级的有C#、erlang、golang*
轻量级有python、lua、javascript、ruby
还有函数式的scala、scheme等。
c/c++不直接支持协程语义,但有不少开源的协程库,如:
Protothreads:一个“蝇量级” C 语言协程库
libco:来自腾讯的开源协程库libco介绍,官网
coroutine:云风的一个C语言同步协程库,详细信息
目前看到大概有四种实现协程的方式:
第一种:利用glibc 的 ucontext组件(云风的库)
第二种:使用汇编代码来切换上下文(实现c协程)
第三种:利用C语言语法switch-case的奇淫技巧来实现(Protothreads)
第四种:利用了 C 语言的 setjmp 和 longjmp( 一种协程的 C/C++ 实现,要求函数里面使用 static local 的变量来保存协程内部的数据)
本篇主要使用ucontext来实现简单的协程库。
2.ucontext初接触
利用ucontext提供的四个函数getcontext(),setcontext(),makecontext(),swapcontext()可以在一个进程中实现用户级的线程切换。
本节我们先来看ucontext实现的一个简单的例子:
[cpp] view plain copy print?
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
int main(int argc, const char *argv[]){
ucontext_t context;
getcontext(&context);
puts(“Hello world”);
sleep(1);
setcontext(&context);
return 0;
}
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
int main(int argc, const char *argv[]){
ucontext_t context;
getcontext(&context);
puts(“Hello world”);
sleep(1);
setcontext(&context);
return 0;
}
注:示例代码来自维基百科.
保存上述代码到example.c,执行编译命令:
gcc example.c -o example
想想程序运行的结果会是什么样?
[plain] view plain copy print?
cxy@ubuntu:~$ ./example
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
^C
cxy@ubuntu:~$
cxy@ubuntu:~$ ./example
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
^C
cxy@ubuntu:~$
上面是程序执行的部分输出,不知道是否和你想得一样呢?我们可以看到,程序在输出第一个“Hello world”后并没有退出程序,而是持续不断的输出”Hello world“。其实是程序通过getcontext先保存了一个上下文,然后输出”Hello world”,在通过setcontext恢复到getcontext的地方,重新执行代码,所以导致程序不断的输出”Hello world“,在我这个菜鸟的眼里,这简直就是一个神奇的跳转。
那么问题来了,ucontext到底是什么?
3.ucontext组件到底是什么
在类System V环境中,在头文件< ucontext.h > 中定义了两个结构类型,mcontext_t和ucontext_t和四个函数getcontext(),setcontext(),makecontext(),swapcontext().利用它们可以在一个进程中实现用户级的线程切换。
mcontext_t类型与机器相关,并且不透明.ucontext_t结构体则至少拥有以下几个域:
[cpp] view plain copy print?
typedef struct ucontext {
struct ucontext *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
…
} ucontext_t;
typedef struct ucontext {
struct ucontext *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
…
} ucontext_t;
当当前上下文(如使用makecontext创建的上下文)运行终止时系统会恢复uc_link指向的上下文;uc_sigmask为该上下文中的阻塞信号集合;uc_stack为该上下文中使用的栈;uc_mcontext保存的上下文的特定机器表示,包括调用线程的特定寄存器等。
下面详细介绍四个函数:
int getcontext(ucontext_t *ucp);
初始化ucp结构体,将当前的上下文保存到ucp中
int setcontext(const ucontext_t *ucp);
设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得,如果调用成功则不返回。如果上下文是通过调用getcontext()取得,程序会继续执行这个调用。如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,如果func函数返回,则恢复makecontext第一个参数指向的上下文第一个参数指向的上下文context_t中指向的uc_link.如果uc_link为NULL,则线程退出。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, …);
makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link.
当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
保存当前上下文到oucp结构体中,然后激活upc上下文。
如果执行成功,getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对于的errno.
简单说来, getcontext获取当前上下文,setcontext设置当前上下文,swapcontext切换上下文,makecontext创建一个新的上下文。
4.小试牛刀-使用ucontext组件实现线程切换
虽然我们称协程是一个用户态的轻量级线程,但实际上多个协程同属一个线程。任意一个时刻,同一个线程不可能同时运行两个协程。如果我们将协程的调度简化为:主函数调用协程1,运行协程1直到协程1返回主函数,主函数在调用协程2,运行协程2直到协程2返回主函数。示意步骤如下:
[cpp] view plain copy print?
执行主函数
切换:主函数 –> 协程1
执行协程1
切换:协程1 –> 主函数
执行主函数
切换:主函数 –> 协程2
执行协程2
切换协程2 –> 主函数
执行主函数
这种设计的关键在于实现主函数到一个协程的切换,然后从协程返回主函数。这样无论是一个协程还是多个协程都能够完成与主函数的切换,从而实现协程的调度。
实现用户线程的过程是:
我们首先调用getcontext获得当前上下文
修改当前上下文ucontext_t来指定新的上下文,如指定栈空间极其大小,设置用户线程执行完后返回的后继上下文(即主函数的上下文)等
调用makecontext创建上下文,并指定用户线程中要执行的函数
切换到用户线程上下文去执行用户线程(如果设置的后继上下文为主函数,则用户线程执行完后会自动返回主函数)。
下面代码context_test函数完成了上面的要求。
[cpp] view plain copy print?
#include <ucontext.h>
#include <stdio.h>
void func1(void * arg)
{
puts(“1”);
puts(“11”);
puts(“111”);
puts(“1111”);
}
void context_test()
{
char stack[1024*128];
ucontext_t child,main;
getcontext(&child); //获取当前上下文
child.uc_stack.ss_sp = stack;//指定栈空间
child.uc_stack.ss_size = sizeof(stack);//指定栈空间大小
child.uc_stack.ss_flags = 0;
child.uc_link = &main;//设置后继上下文
makecontext(&child,(void (*)(void))func1,0);//修改上下文指向func1函数
swapcontext(&main,&child);//切换到child上下文,保存当前上下文到main
puts(“main”);//如果设置了后继上下文,func1函数指向完后会返回此处
}
int main()
{
context_test();
return 0;
}
在context_test中,创建了一个用户线程child,其运行的函数为func1.指定后继上下文为main
func1返回后激活后继上下文,继续执行主函数。
保存上面代码到example-switch.cpp.运行编译命令:
g++ example-switch.cpp -o example-switch
执行程序结果如下
[cpp] view plain copy print?
cxy@ubuntu:~$ ./example-switch
1
11
111
1111
main
cxy@ubuntu:~$
你也可以通过修改后继上下文的设置,来观察程序的行为。如修改代码
child.uc_link = &main;为child.uc_link = NULL;
再重新编译执行,其执行结果为:
[cpp] view plain copy print?
cxy@ubuntu:~$ ./example-switch
1
11
111
1111
cxy@ubuntu:~$
可以发现程序没有打印”main”,执行为func1后直接退出,而没有返回主函数。可见,如果要实现主函数到线程的切换并返回,指定后继上下文是非常重要的。
5.使用ucontext实现自己的线程库
掌握了上一节从主函数到协程的切换的关键,我们就可以开始考虑实现自己的协程了。
定义一个协程的结构体如下:
[cpp] view plain copy print?
typedef void (*Fun)(void *arg);
typedef struct uthread_t
{
ucontext_t ctx;
Fun func;
void *arg;
enum ThreadState state;
char stack[DEFAULT_STACK_SZIE];
}uthread_t;
typedef void (*Fun)(void *arg);
typedef struct uthread_t
{
ucontext_t ctx;
Fun func;
void *arg;
enum ThreadState state;
char stack[DEFAULT_STACK_SZIE];
}uthread_t;
ctx保存协程的上下文,stack为协程的栈,栈大小默认为DEFAULT_STACK_SZIE=128Kb.你可以根据自己的需求更改栈的大小。func为协程执行的用户函数,arg为func的参数,state表示协程的运行状态,包括FREE,RUNNABLE,RUNING,SUSPEND,分别表示空闲,就绪,正在执行和挂起四种状态。
在定义一个调度器的结构体
[cpp] view plain copy print?
typedef std::vector
typedef struct schedule_t
{
ucontext_t main;
int running_thread;
Thread_vector threads;
schedule_t():running_thread(-1){}
}schedule_t;
typedef std::vector
typedef struct schedule_t
{
ucontext_t main;
int running_thread;
Thread_vector threads;
schedule_t():running_thread(-1){}
}schedule_t;
调度器包括主函数的上下文main,包含当前调度器拥有的所有协程的vector类型的threads,以及指向当前正在执行的协程的编号running_thread.如果当前没有正在执行的协程时,running_thread=-1.
接下来,在定义几个使用函数uthread_create,uthread_yield,uthread_resume函数已经辅助函数schedule_finished.就可以了。
int uthread_create(schedule_t &schedule,Fun func,void *arg);
创建一个协程,该协程的会加入到schedule的协程序列中,func为其执行的函数,arg为func的执行函数。返回创建的线程在schedule中的编号。
void uthread_yield(schedule_t &schedule);
挂起调度器schedule中当前正在执行的协程,切换到主函数。
void uthread_resume(schedule_t &schedule,int id);
恢复运行调度器schedule中编号为id的协程
int schedule_finished(const schedule_t &schedule);
判断schedule中所有的协程是否都执行完毕,是返回1,否则返回0.注意:如果有协程处于挂起状态时算作未全部执行完毕,返回0.
代码就不全贴出来了,我们来看看两个关键的函数的具体实现。首先是uthread_resume函数:
[cpp] view plain copy print?
void uthread_resume(schedule_t &schedule , int id)
{
if(id < 0 || id >= schedule.threads.size()){
return;
}
uthread_t *t = &(schedule.threads[id]);
switch(t->state){
case RUNNABLE:
getcontext(&(t->ctx));
t->ctx.uc_stack.ss_sp = t->stack;
t->ctx.uc_stack.ss_size = DEFAULT_STACK_SZIE;
t->ctx.uc_stack.ss_flags = 0;
t->ctx.uc_link = &(schedule.main);
t->state = RUNNING;
schedule.running_thread = id;
makecontext(&(t->ctx),(void (*)(void))(uthread_body),1,&schedule);
/* !! note : Here does not need to break */
case SUSPEND:
swapcontext(&(schedule.main),&(t->ctx));
break;
default: ;
}
}
void uthread_resume(schedule_t &schedule , int id)
{
if(id < 0 || id >= schedule.threads.size()){
return;
}
uthread_t *t = &(schedule.threads[id]);
switch(t->state){
case RUNNABLE:
getcontext(&(t->ctx));
t->ctx.uc_stack.ss_sp = t->stack;
t->ctx.uc_stack.ss_size = DEFAULT_STACK_SZIE;
t->ctx.uc_stack.ss_flags = 0;
t->ctx.uc_link = &(schedule.main);
t->state = RUNNING;
schedule.running_thread = id;
makecontext(&(t->ctx),(void (*)(void))(uthread_body),1,&schedule);
/* !! note : Here does not need to break */
case SUSPEND:
swapcontext(&(schedule.main),&(t->ctx));
break;
default: ;
}
}
如果指定的协程是首次运行,处于RUNNABLE状态,则创建一个上下文,然后切换到该上下文。如果指定的协程已经运行过,处于SUSPEND状态,则直接切换到该上下文即可。代码中需要注意RUNNBALE状态的地方不需要break.
[cpp] view plain copy print?
void uthread_yield(schedule_t &schedule)
{
if(schedule.running_thread != -1 ){
uthread_t *t = &(schedule.threads[schedule.running_thread]);
t->state = SUSPEND;
schedule.running_thread = -1;
swapcontext(&(t->ctx),&(schedule.main));
}
}
void uthread_yield(schedule_t &schedule)
{
if(schedule.running_thread != -1 ){
uthread_t *t = &(schedule.threads[schedule.running_thread]);
t->state = SUSPEND;
schedule.running_thread = -1;
swapcontext(&(t->ctx),&(schedule.main));
}
}
uthread_yield挂起当前正在运行的协程。首先是将running_thread置为-1,将正在运行的协程的状态置为SUSPEND,最后切换到主函数上下文。
更具体的代码我已经放到github上,点击这里。
6.最后一步-使用我们自己的协程库
保存下面代码到example-uthread.cpp.
[cpp] view plain copy print?
#include “uthread.h”
#include <stdio.h>
void func2(void * arg)
{
puts(“22”);
puts(“22”);
uthread_yield(*(schedule_t *)arg);
puts(“22”);
puts(“22”);
}
void func3(void *arg)
{
puts(“3333”);
puts(“3333”);
uthread_yield(*(schedule_t *)arg);
puts(“3333”);
puts(“3333”);
}
void schedule_test()
{
schedule_t s;
int id1 = uthread_create(s,func3,&s);
int id2 = uthread_create(s,func2,&s);
while(!schedule_finished(s)){
uthread_resume(s,id2);
uthread_resume(s,id1);
}
puts(“main over”);
}
int main()
{
schedule_test();
return 0;
}
#include “uthread.h”
#include <stdio.h>
void func2(void * arg)
{
puts(“22”);
puts(“22”);
uthread_yield(*(schedule_t *)arg);
puts(“22”);
puts(“22”);
}
void func3(void *arg)
{
puts(“3333”);
puts(“3333”);
uthread_yield(*(schedule_t *)arg);
puts(“3333”);
puts(“3333”);
}
void schedule_test()
{
schedule_t s;
int id1 = uthread_create(s,func3,&s);
int id2 = uthread_create(s,func2,&s);
while(!schedule_finished(s)){
uthread_resume(s,id2);
uthread_resume(s,id1);
}
puts(“main over”);
}
int main()
{
schedule_test();
return 0;
}
执行编译命令并运行:
g++ example-uthread.cpp -o example-uthread
./example-uthread
运行结果如下:
[cpp] view plain copy print?
cxy@ubuntu:~/mythread$./example-uthread
22
22
3333
3333
22
22
3333
3333
main over
cxy@ubuntu:~/mythread$
cxy@ubuntu:~/mythread$./example-uthread
22
22
3333
3333
22
22
3333
3333
main over
cxy@ubuntu:~/mythread$
可以看到,程序协程func2,然后切换到主函数,在执行协程func3,再切换到主函数,又切换到func2,在切换到主函数,再切换到func3,最后切换到主函数结束。
总结一下,我们利用getcontext和makecontext创建上下文,设置后继的上下文到主函数,设置每个协程的栈空间。在利用swapcontext在主函数和协程之间进行切换。
到此,使用ucontext做一个自己的协程库就到此结束了。相信你也可以自己完成自己的协程库了。
最后,代码我已经放到github上,点击这里。
6.8.7 Linux下使用popen()执行shell命令
简单说一下popen()函数
函数定义****
#include <stdio.h>
FILE * popen(const char *command , const char *type );
int pclose(FILE *stream);
函数说明****
popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
type参数只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。如果type是”r”则文件指针连接到command的标准输出;如果type是”w”则文件指针连接到command的标准输入。
command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。
popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同。
返回值****
如果调用fork()或pipe()失败,或者不能分配内存将返回NULL,否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。
附上一个例子:
//execute shell command
//执行一个shell命令,输出结果逐行存储在resvec中,并返回行数
int32_t myexec(const char *cmd, vector
resvec.clear();
FILE *pp = popen(cmd, “r”); //建立管道
if (!pp) {
return -1;
}
char tmp[1024]; //设置一个合适的长度,以存储每一行输出
while (fgets(tmp, sizeof(tmp), pp) != NULL) {
if (tmp[strlen(tmp) - 1] == ‘\n’) {
tmp[strlen(tmp) - 1] = ‘\0’; //去除换行符
}
resvec.push_back(tmp);
}
pclose(pp); //关闭管道
return resvec.size();
}
第 7 章 new
7.1 gcc选项-g与-rdynamic的异同
gcc 的 -g ,应该没有人不知道它是一个调试选项,因此在一般需要进行程序调试的场景下,我们都会加上该选项,并且根据调试工具的不同,还能直接选择更有针对性的说明,比如 -ggdb 。-g是一个编译选项,即在源代码编译的过程中起作用,让gcc把更多调试信息(也就包括符号信息)收集起来并将存放到最终的可执行文件内。
相比-g选项, -rdynamic 却是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。
看示例:
[root@www c]# cat t.c
#include <stdio.h>
void bar() {}
void baz() {}
void foo() {}
int main() { foo(); printf(“test”); return 0; }
对于上面的示例代码,普通和加-g编译:
[root@www c]# uname -a
Linux www.t1.com 2.6.38.8 #2 SMP Wed Nov 2 07:52:53 CST 2011 x86_64 x86_64 x86_64 GNU/Linux
[root@www c]# gcc -O0 -o t t.c
[root@www c]# gcc -O0 -g -o t.g t.c
[root@www c]# readelf -a t > t.elf
[root@www c]# readelf -a t.g > t.g.elf
[root@www c]# ls -lh *.elf t t.g
-rwxr-xr-x. 1 root root 6.6K Jul 24 06:50 t
-rw-r–r–. 1 root root 15K Jul 24 06:51 t.elf
-rwxr-xr-x. 1 root root 7.9K Jul 24 06:50 t.g
-rw-r–r–. 1 root root 16K Jul 24 06:51 t.g.elf
加-g编译后,因为包含了debug信息,因此生成的可执行文件偏大(程序本身非常小,所以增加的调试信息不多)。
看-g编译的符号表:
[root@www c]# readelf -s t
Symbol table ‘.dynsym’ contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
Symbol table ‘.symtab’ contains 67 entries:
Num: Value Size Type Bind Vis Ndx Name
…
48: 00000000004003e0 0 FUNC GLOBAL DEFAULT 13 _start
49: 00000000004004c4 6 FUNC GLOBAL DEFAULT 13 bar
…
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@@GLIBC_2.2.5
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
55: 00000000004005e8 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
56: 00000000004004d0 6 FUNC GLOBAL DEFAULT 13 foo
…
64: 00000000004004d6 31 FUNC GLOBAL DEFAULT 13 main
65: 0000000000400390 0 FUNC GLOBAL DEFAULT 11 _init
66: 00000000004004ca 6 FUNC GLOBAL DEFAULT 13 baz
注意.dynsym表,只有该程序用到的几个外部动态符号存在。
加-rdynamic选项编译,readelf查看:
[root@www c]# gcc -O0 -rdynamic -o t.rd t.c
[root@www c]*# readelf -s t.rd *
Symbol table ‘.dynsym’ contains 20 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
5: 0000000000400724 6 FUNC GLOBAL DEFAULT 13 bar
6: 0000000000400730 6 FUNC GLOBAL DEFAULT 13 foo
7: 0000000000600b68 0 NOTYPE GLOBAL DEFAULT 24 __data_start
8: 0000000000600b80 0 NOTYPE GLOBAL DEFAULT ABS _end
9: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS _edata
10: 0000000000600b68 0 NOTYPE WEAK DEFAULT 24 data_start
11: 0000000000400640 0 FUNC GLOBAL DEFAULT 13 _start
12: 0000000000400848 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
13: 0000000000400770 137 FUNC GLOBAL DEFAULT 13 __libc_csu_init
14: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
15: 0000000000400736 39 FUNC GLOBAL DEFAULT 13 main
16: 00000000004005f0 0 FUNC GLOBAL DEFAULT 11 _init
17: 0000000000400760 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
18: 0000000000400838 0 FUNC GLOBAL DEFAULT 14 _fini
19: 000000000040072a 6 FUNC GLOBAL DEFAULT 13 baz
Symbol table ‘.symtab’ contains 67 entries:
Num: Value Size Type Bind Vis Ndx Name
…
50: 0000000000400640 0 FUNC GLOBAL DEFAULT 13 _start
51: 0000000000400724 6 FUNC GLOBAL DEFAULT 13 bar
…
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@@GLIBC_2.2.5
56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
57: 0000000000400848 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
58: 0000000000400730 6 FUNC GLOBAL DEFAULT 13 foo
…
64: 0000000000400736 31 FUNC GLOBAL DEFAULT 13 main
65: 00000000004005f0 0 FUNC GLOBAL DEFAULT 11 _init
66: 000000000040072a 6 FUNC GLOBAL DEFAULT 13 baz
[root@www c]#
可以看到添加-rdynamic选项后,.dynsym表就包含了所有的符号,不仅是已使用到的外部动态符号,还包括本程序内定义的符号,比如bar、foo、baz等。
.dynsym表里的数据并不能被strip掉:
[root@www c]# strip t.rd
[root@www c]# readelf -s t.rd
Symbol table ‘.dynsym’ contains 20 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
5: 0000000000400724 6 FUNC GLOBAL DEFAULT 13 bar
6: 0000000000400730 6 FUNC GLOBAL DEFAULT 13 foo
7: 0000000000600b68 0 NOTYPE GLOBAL DEFAULT 24 __data_start
8: 0000000000600b80 0 NOTYPE GLOBAL DEFAULT ABS _end
9: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS _edata
10: 0000000000600b68 0 NOTYPE WEAK DEFAULT 24 data_start
11: 0000000000400640 0 FUNC GLOBAL DEFAULT 13 _start
12: 0000000000400848 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
13: 0000000000400770 137 FUNC GLOBAL DEFAULT 13 __libc_csu_init
14: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
15: 0000000000400736 39 FUNC GLOBAL DEFAULT 13 main
16: 00000000004005f0 0 FUNC GLOBAL DEFAULT 11 _init
17: 0000000000400760 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
18: 0000000000400838 0 FUNC GLOBAL DEFAULT 14 _fini
19: 000000000040072a 6 FUNC GLOBAL DEFAULT 13 baz
简单总结一下-g选项与-rdynamic选项的差别:
1,-g选项新添加的是调试信息(一系列.debug_xxx段),被相关调试工具,比如gdb使用,可以被strip掉。
2,-rdynamic选项新添加的是动态连接符号信息,用于动态连接功能,比如dlopen()系列函数、backtrace()系列函数使用,不能被strip掉,即强制strip将导致程序无法执行:
[root@www c]# ./t.rd
test[root@www c]# strip -R .dynsym t.rd
[root@www c]# ./t.rd
./t.rd: relocation error: ./t.rd: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
[root@www c]#
3,.symtab表在程序加载时会被加载器 丢弃 ,gdb等调试工具由于可以直接访问到磁盘上的二进制程序文件:
[root@www c]# gdb t.g -q
Reading symbols from /home/work/dladdr/c/t.g…done.
(gdb)
因此可以使用所有的调试信息,这包括.symtab表;而backtrace()系列函数作为程序执行的逻辑功能,无法去读取磁盘上的二进制程序文件,因此只能使用.dynsym表。
其它几个工具可以动态指定查看,比如nm、objdump:
[root@www c]# nm t.rd
nm: t.rd: no symbols
[root@www c]# nm -D t.rd
0000000000400848 R _IO_stdin_used
w _Jv_RegisterClasses
0000000000600b6c A __bss_start
0000000000600b68 D __data_start
w gmon_start
0000000000400760 T __libc_csu_fini
0000000000400770 T __libc_csu_init
U __libc_start_main
0000000000600b6c A _edata
0000000000600b80 A _end
0000000000400838 T _fini
00000000004005f0 T _init
0000000000400640 T _start
0000000000400724 T bar
000000000040072a T baz
0000000000600b68 W data_start
0000000000400730 T foo
0000000000400736 T main
U printf
[root@www c]#
[root@www c]# objdump -T t.rd
t.rd: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF UND 0000000000000000 GLIBC_2.2.5 printf
0000000000000000 w D UND 0000000000000000 gmon_start
0000000000000000 w D UND 0000000000000000 _Jv_RegisterClasses
0000000000000000 DF UND 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000400724 g DF .text 0000000000000006 Base bar
0000000000400730 g DF .text 0000000000000006 Base foo
0000000000600b68 g D .data 0000000000000000 Base __data_start
0000000000600b80 g D ABS 0000000000000000 Base _end
0000000000600b6c g D ABS 0000000000000000 Base _edata
0000000000600b68 w D .data 0000000000000000 Base data_start
0000000000400640 g DF .text 0000000000000000 Base _start
0000000000400848 g DO .rodata 0000000000000004 Base _IO_stdin_used
0000000000400770 g DF .text 0000000000000089 Base __libc_csu_init
0000000000600b6c g D ABS 0000000000000000 Base __bss_start
0000000000400736 g DF .text 0000000000000027 Base main
00000000004005f0 g DF .init 0000000000000000 Base _init
0000000000400760 g DF .text 0000000000000002 Base __libc_csu_fini
0000000000400838 g DF .fini 0000000000000000 Base _fini
000000000040072a g DF .text 0000000000000006 Base baz
4,-rdynamic选项不产生任何调试信息,因此在一般情况下,新增的附加信息比-g选项要少得多。除非是完全的静态连接,否则即便是没有加-rdynamic选项,程序使用到的外部动态符号,比如前面示例里的printf,也会被自动加入到.dynsym表。