940 likes | 1.12k Views
操作系统. 第二章 软件开发工具. 陆松年 snlu@sjtu.edu.cn. 12.1 文本编辑器 vi. 12.1.1 vi 概要. vi [ 文件 … ] 通常一次只编辑一个文件。如果指定的文件不存在, vi 就为你创建一个。如果不指定文件名,则必须在退出 vi 时指定存入的文件。 在编辑一个已存在的文件时, vi 将磁盘中文件的内容拷贝至内存编辑缓冲区,所有的输入、修改等操作都仅对编辑缓冲区内的数据起作用,只有当要求 vi 写入文件时,才用编辑缓冲区的内容替换原文件的内容。. Vi 的基本工作方法.
E N D
操作系统 第二章 软件开发工具 陆松年 snlu@sjtu.edu.cn
12.1 文本编辑器vi 12.1.1 vi 概要 vi [文件…] • 通常一次只编辑一个文件。如果指定的文件不存在,vi就为你创建一个。如果不指定文件名,则必须在退出vi时指定存入的文件。 • 在编辑一个已存在的文件时,vi将磁盘中文件的内容拷贝至内存编辑缓冲区,所有的输入、修改等操作都仅对编辑缓冲区内的数据起作用,只有当要求vi写入文件时,才用编辑缓冲区的内容替换原文件的内容。
Vi的基本工作方法 • vi有两种基本工作方法,一种是输入方法,另一种是命令方式。 • 在输入方式,用户键入的任何内容都插到编辑缓冲区,直到按下ESC键才退出输入方式,回到命令方式。 • 在命令方式,用户键入的内容作为命令解释,以执行vi提供的几十条编辑功能。 • 一种特殊的命令方式是底行命令方式,即在输入命令‘:’时,该字符连同光标移到了屏幕的底行,在该行上可执行一切行编辑命令。
12.1.2 屏幕编辑命令 (一)移动光标命令 1. 以字符为单位移动(包括垂直方向) 在命令状态下光标控制键←、↑、→、↓ 使用一些单字母的命令来移动光标: h 光标左移一个字符单位 j 光标下移一个字符单位 k 光标上移一个字符单位 l 光标右移一个字符单位
2. 以字、句子、段落为单位移动 w 光标移到下一字的首字符处 3. 行首尾移动 0 光标移到当前行行首(第一列) ^ 光标移到当前行中第一个 非空白字符处 $ 光标移到当前行行尾 4. 以行为单位移动 n RETURN 光标下移n行 G 光标移到文件尾
5. 以屏幕为单位移动 要使光标快速地在垂直方向移动以浏览或处理文件可以使用以下命令: H (Hight) 光标移到屏幕顶部 M (Middle) 光标移到屏幕中部 L (Low) 光标移到屏幕底部 以下命令使得编辑缓冲区数据在屏幕上“滚动”,而光标在屏幕上的相对位置并不改变: ^F (Fowlow) 下移一屏 ^B (Back) 上移一屏
(二)输入方式 i(或a) 在光标之前(或之后)插入 o(或O) 在当前行下(或上)面插入 • 在输入过程中,你如果打错了一个或多个字符,可以用BACKSPACE(或DELETE)键删除它们。 • 注意,在一些终端上,插入过程中不要使用 ←、↑、→、↓键(在命令行输入命令时也一样)。 • 在删除了这些字符后,你可以接着输入正确的数据,新输入的字符,将覆盖被删字符。
(三)删除命令 在用户输入数据并用ESC键退出了插入状态回到了命令状态后,就可以用删除、修改等命令修改正文。删除命令主要有以下几个: x 删除光标位置的一个字符 dw 删除光标位置的一个字 d$(或D) 删除自光标到本行未间的正文 dd 删除当前行 dG 从当前行一直删到最后一行 d1G 从当前行一直删到第一行 大部分的删除命令都可带有重复数,如 10x 删除自光标位置起的10个字符 d5w 删除自光标位位置起的5个字
(四)修改命令 1. 一对一的字符替换(replace) r 字符 替换光标所在字符,如 ra 用字符a替换光标所在字符 3rabc 用abc三个字符替换 自光标起的三个字符 R输入串 用输入串一对一地替换自光标位置起的任意多个字符,直至按ESC键 2. 替换指定数目的字符和行(substitude) s 替换指定数目的字符,如 3sxyz 用xyz替换光标开始处的三个字符,按ESC键退出替换,但输入部可以多于3个字符,也可少于3个字符。
(五)缓冲区与删除、抽取、放置命令 1. 通用缓冲区 • vi编辑程序有一个通用缓冲区和26个有名缓冲区。所有的删除命令还把被删正文置入通用缓冲区。用户可以用p(put)命令将通用缓冲区的内容取出放置到指定位置,故用删除命令和放置命令的组合可以实现把编辑缓冲区中的数据从一个位置搬移到另一个位置。例如: • d5w命令删去光标所处位置起的五个字,并把删去的5个字置入通用缓冲区。将光标移动到新的位置后用p命令可使这5个字插到新的位置上。又如: • 先执行3dd命令,将光标移到指定行后再执行p命令就把原光标所在位置起的三行正文搬到了新的位置。
如果用户不想删去正文就可以使用y(yank)命令。用y命令和p命令的组合可以实现数据的拷贝。如使用命令如果用户不想删去正文就可以使用y(yank)命令。用y命令和p命令的组合可以实现数据的拷贝。如使用命令 2yy 抽取当前行及下一行,将光标移到新的位置后用p命令把这二行拷贝至新的当前行之后。类似地y3w抽取3个字,而4y3w则抽取12个字。
(六)其他命令 u 废除上一条命令所做的修改 . 重复执行上一个命令
12.1.3底行命令 在vi命令状态下,键入‘/’,‘?’或‘:’,光标就跳到屏幕的最底行,在该行可以输入对文件操作的命令。
(一)搜索命令 • 当用户键入了‘/’后,vi在最底行(命令行)显示字符‘/’和光标,接下来就可以键入查找模式了。可以用正则表达式指定模式。 • 用‘/’命令查找模式时,从当前的光标位置向下查找匹配模式的第一个字符串,光标停留在该字符串的第一个字符上。 • ‘?’命令是‘/’命令的反向搜索方式。使用n(next)命令,用户不必输入模式就可以按原方向继续上一次的查找。使用N命令则按反方向继续查找。例: • /\ < m[ae].*y 查找一个字的首部含有m[ae].*y模式的串。该模式将匹配many,may,merry等单词。
(二)字符串替代 • :[地址] s/模式/替代串[/gcp] ,例: :s/\<many\>/a lot of/ • 在替代之前请求确认,可以再加上‘c’ :s/UNIX/Unix/cg • 要删除指定模式的串,可将替代串部分置为空: :s/S[ae]*ing//gp • 字符串替代命令中如不指定地址部分,则仅对当前行执行替换,否则替代命令在指定的地址范围内执行。
地址部分格式举例如下: 地址 搜索范围 15 第15行 5,15 从第5行至第15行 1,. 从第1行至当前行 .,$ 从当前行至最后一行 .,+25 从当前行至当前行后25行 1,$ 文件中的全部行 例: :1,$s/ten/10/g 在整个文件中用字符串10替代字符串ten,即: ten->10, often->of10, tenant->10ant :1,$s/\<ten\>/10/g 仅对单词ten替换成10
(三)行编辑命令 删除命令 • :[地址]d删除地址部分指定的文件行, 如不指定地址,则只删除当前行。如 • :.,-10d 删去当前行至当前行上面10行(共11行) 行移动 • :[地址]m[单地址] 将地址部分指定的文件行移到起始地址为单地址的地方。 例: • :10m20 将第10行移到第20行之下
(四)读写文件 读文件命令的模式为 • :[单地址]r文件 将文件中内容插到指定的地址位置后,地址缺省为当前行,例: • :$r myfile 将文件myfile中全部内容插到编辑文件尾。 写文件的格式为 • :[地址]w[文件] 如不指定地址,则将整个编辑缓冲区写入文件; 如不指定文件,则将缓冲区数据写入调用vi指定的原文件; 如指定的文件已在,而你确实想覆盖它,可用w!file命令。
隔一段时间打一下w命令较为安全。还可使用添加写的命令,如隔一段时间打一下w命令较为安全。还可使用添加写的命令,如 • :w>>file 将编辑文件添加到磁盘文件file的后面。 • :10,20w>>example 将第10行至第20行添加到文件example之后 • 如果用户曾在vi中修改过正文,则先要执行:w命令将编辑文件盘后才能用:q命令退出vi,或直接执行保存文件后退出vi的命令 :wq • 不存盘而强制退出的命令为 :q!
(六)设置参数命令 • :set nu(mber) 在每一文件行前面显示行号 • :set nonu(mber) 取消行号,所有的设置命令都可以在参数前加上“no”取消。 • 屏幕上显示的行号在编辑缓冲区中是不存在的,故在存盘时不会把行号存入文件中。
12.1.4 在vi程序中执行shell命令 :! shell命令 • 在shell命令执行完成后,按RETURN返回vi。 • 利用在vi中执行shell命令的功能可使用户在编写、调试源程序不必经常要进出vi。 • 源程序输入完成后先用:w命令存盘,然后在vi中执行编译程序,如 !cc % • 命令中‘%’代表当前文件名,如果你的系统不支持‘%’,也可用 “!cc 源文件”或“!cc 源文件 -o目标文件” 命令。记下编译时的出错信息,按RETURN返回到vi中去修改。
最基本的vi命令 vi filename.c i(或a) 在光标之前(或之后)插入 o(或O) 在当前行下(或上)面插入 BACKSPACE(或DELETE)键删除 ESC键退出插入状态 h 光标左移一个字符单位 j 光标下移一个字符单位 k 光标上移一个字符单位 l 光标右移一个字符单位 0 光标移到当前行行首(第一列) $ 光标移到当前行行尾保存文件后退出vi ^F (Fowlow) 下移一屏 ^B (Back) 上移一屏
x 删除光标位置的一个字符 dd 删除当前行 ra 用字符a替换光标所在字符 nsstr 用str替换光标开始处的n个字符 nyy 将n行数据拷贝至缓冲区 p 把缓冲区行拷贝至新的当前行之后 u 废除上一条命令所做的修改 :set nu(mber) 在文件行前面显示行号 :set nonu(mber) 取消行号
/str 搜索指定模式的字符串str :[地址] s/模式/替代串[/g] 对字符串进行替换 :w [file] 则将整个编辑缓冲区写入文件 :! shell命令 在vi程序中执行shell命令 :wq 保存文件后退出vi :q! 不保存文件退出vi
12.2 C编译系统 12.2.1 C编译基本过程 • cc是UNIX系统中的c语言编译程序,它能完成宏扩展、文件包括、编译、优化、汇编连接和装入等多种功能。 • cc编译命令的一般形式是: cc [ 选项 ] 源程序和目标代码文件表 [-l库...]
2. 编译选项 影响cc编译阶段的选项是: • -g调试选项。这个选项使得编译程序在最终的可执行目标文件中包含附加的关于变量和源程序语句的信息,这些信息对于运行符号调试程序sdb是必须的。 • -O优化选项。该选项调用外加的编译步骤——优化程序,以生成更有效的代码。
12.2.5 连接装入程序 扫描命令行中的目标文件和库文件参数 libc.a: 标准c函数库,当cc生成二进制可执行文件时自动搜索该库。 libm.a: 数学函数库。 libplot.a 绘图子程序库。
连接/装入选项 • -o name该选项使得最后生成的二进制可执行代码存入名为name的文件中,如不用-o选项,则存入名为a.out的文件中。 • -lx这个选项是档案库名/lib/libx.a的缩写。如果该档案库不存在,ld程序再看/usr/lib/libx.a。库名参数x不限于单个字符,它可以是一个字符串。 • -Ldir当用-lx指定档案库时,先在-L选项中指出的dir目录中查找档案库, 再到/lib和/usr/lib中查找。
12.2.6 一个使用cc的综合例子 • cc -Ddebug -O example.c more.c io.s unit.o -o example -lm • cc首先调用预处理程序,将源程序中说明的头文件包含进来,并将标识符debug的值置为1,处理有关条件编译的事务; • 然后调用编译程序,生成example.c,more.c的汇编语言目标程序example.s和more.s,并对它们进行优化; • 再调用汇编程序生成目标代码example.o、more.o和io.o; • 最后调用连接/装入程序将cc生成的目标代码,参数表中列出的目标代码和数学库中的一些档案文件连接起来,对它们进行重定位,并生成可执行的二进制文件example。
12.3 库的维护 • 一个库文件是由一组文件通过运行ar库维护程序构成的,多数库文件是目标文件的集合。 • 如C的标准库文件/lib/libc.a中就含有C的标准子程序及系统调用的全部目标文件。这些目标文件是由编译相应的.c文件集合而得到的。 • 按约定,库文件名字以lib开头,以.a为后缀。 12.3.1 库的概念
库文件与目标文件的差异 • 尽管库文件与目标文件很相似,但是它们之间还是有关键的差异。当用户把几个目标文件连接成一个大程序时,这些目标文件中所有的内容都成为最终的可执行程序的一部分,根本不管你是否使用了其中的代码。 • 当编译程序连接库的时候,用户在源程序要用到什么函数,编译程序就将库中对应的函数连接到用户的程序中。连接/装入程序把从库中取出的目标代码文件的符号表和二进制制可执行文件的符号表合并起来。
12.3.2 库的维护 1. 创建新档案库或把文件加到现存档案库中 • 这是一个最重要的库管理程序。 ar r[v] 库名 目标文件表 • 如将目标文件filea.o,fileb.o,filec.o加入库文件libfile.a中可用命令 ar rv libfile.a filea.o fileb.o filec.o • 也可利用模式生成文件名: ar rv libfile.a file[a-c].o ar新建立包含这三个目标文件的库。
与很多的UNIX系统程序不一样,ar的选项(rv)的前面没有‘-’,但这不会造成二义性,因为ar程序至少要求一个选项。与很多的UNIX系统程序不一样,ar的选项(rv)的前面没有‘-’,但这不会造成二义性,因为ar程序至少要求一个选项。 • 选项r表示将指定的文件加入库中,必要时创建库文件。如果库中已有要加入的文件,则这个文件被新文件所代替。 • 选项v表示详细(verbose)方式,使ar将每一完成的操作都显示出来。 • ar会自动地产生与目标文件外部符号名有关的符号表。
2. 列出库中的文件 • 这也是一个常用的命令,其格式为: ar t[v] 库 [文件表] • 如指定文件表参数,则只列出这些文件。当选项v与t一起使用时,列出文件的详细描述,这类似于ls -l命令的l选项。如命令: ar tv libfile.a • 以详细方式按序列出库文件libfile.a中的所有文件。
3. 从库中提取文件(extract) ar x libfile.a fileb.o • 从库中提取fileb.o,存放在当前目录下。该操作不改变库的内容。 • 如果不提供目标文件名,则提取库中所有的文件。一般说,提取源代码或正文的档案要比提取目标代码的档案用的更普遍。 • 如选项v与x一起使用,则详细方式
12.3.3 动态库 • 前面介绍的库称为静态库。在编辑、连接程序时,编译程序将用户程序要调用的库函数全部连接到用户的程序中,组合成一个完整的不可分割的执行代码。在运行时,用户程序就与原库脱离了联系。 • 如果有若干的应用程序要调用相同的库函数,那么各个应用分别都要把库中有关的函数连入自己的程序库中,这些冗余的代码极大地增加了程序的长度,平时要消耗较多的磁盘存储空间,这些应用同时运用时也要占用较大的内存空间。
动态库的原理 • 动态库的原理类似于共享代码段,当用户在编译行中指定库时,编译程序并不将动态库中的函数连入用户的程序中,而只建立了对动态库的引用连接,因此所生成的目标代码长度要比使用静态库时小得多。 • 在实际装入运行时,系统根据用户程序中的引用连接信息,再把动态库从盘中调入内存。 • 当有若干程序要使用同一个动态库时,系统只要在内存中驻留该动态库的一个副本,各个调用程序共享同一个库的代码,这样大大减小了系统中执行代码的总长度,也使更多的用户程序可以并发地运行。
从另一方面讲,如果系统中只有一个程序要使用库,那么采用动态库的方式比起采用静态库的方式非但不能减少执行代码的总长度,反而可能增加其长度。从另一方面讲,如果系统中只有一个程序要使用库,那么采用动态库的方式比起采用静态库的方式非但不能减少执行代码的总长度,反而可能增加其长度。 • 因为在连接静态库时,系统只把用户要调用的库函数连入程序中。 • 而在连接动态库时,尽管所生成的目标代码中不包含库中的函数代码,但在执行时系统却要把整个动态库度都调入内存(在虚拟存储管理系统中只要部分调入即可),而不管是否要用到库中的全部函数。
由静态库生成对应的动态库 方法是先从静态库中抽出所有的目标代码(以.o为后缀),再由这些目标代码组合而生成动态库。 在一个不含其他目标代码的临时目录中,可通过执行以下命令产生动态库libisode.so: ar x libisode.a ld -G *.o -o libisode.so
12.4 维护程序的程序Make • 多数的程序设计项目包含了大量的程序或模块。当系统要修改或增强功能时,这些源程序要经历各种变化,对一个源程序的改动就会使本模块或其他模块不能照原样工作。 • 在大型软件开发项目中,要跟踪所有文件的变化是非常困难的。如果没有Make,在修改了一些文件后为了安装新的程序版本,所有的源程序都要重新编译和连接,否则将有很大的风险,因为要判断哪些程序才真正需要重新编译和连接是一个繁琐而乏味的过程。
Make的功能 • Make根据一些说明和文件的修改时间自动地维护模块间的关系。 • 说明规定了模块之间的依赖关系以及为更新目标文件必须执行的动作或命令。Make的说明是存放在makefile或Makefile中的。 • Makefile定义程序是由哪些文件构成的,并用依赖性表示文件之间的相互联系。 • Make的主要责任是重新编译任何最近一次编译以后又已修改过的源文件以及任何依赖于其他已改变模块的源文件。 • 只有依赖的文件修改或更新以后,而被依赖的目标文件还没有被更新,换句话说,目标文件已经“过时”了,就用跟随着依赖行后的构造命令更新目标文件。
12.4.1 Make使用初步 如有一个网络程序network,它是由编译二个源程序subnet.c和service.c而生成的。二个源程序都包含一个公共的头文件netdefs.h,它们之间的关系见图11-2。
头文件 netdefs.h 预处理 源程序文件 subnet.c service.c 编译 目标代码文件 subnet.o service.o 连接 可执行代码文件 network 图11-2 一个示例软件系统的关系图
Make的依赖关系 • 在Makefile中,以非TAB符开始有冒号的一行描述文件的依赖关系,称为依赖行。 • 冒号左边的文件是目标文件,冒号右边的文件是目标文件所依赖的文件或预需的文件。 • 依赖行下面以至少一个TAB符开始的若干行描述了一组动作,即当依赖行冒号右边的文件得到更新从而使得左边的目标文件已“过时”后,就执行依赖行下面各个动作行所描述的一系列UNIX命令。 • 在目标文件右面的依赖也可以为空,在依赖文件为空的情况下,该依赖行下面的命令就无条件地执行。
network的依赖关系与动作 • 对于本例,因为程序network依赖于两个目标代码文件subnet.o和service.o,故只要这两个目标代码文件中的任一个在生成了执行程序network之后又得到了更新,程序network就过时了,应当重新连接subnet.o和service.o,以生成network的新版本。 • 这样的依赖关系与动作在Makefile中可表示为: network: subnet.o service.o cc -o network subnet.o service.o
类似地,subnet.o依赖于subnet.c,又由于subnet.c中含有一个包含netdefs.h文件的编译控制行,所以编译时的正文实际来自于两个文件:subnet.c和netdefs.h。类似地,subnet.o依赖于subnet.c,又由于subnet.c中含有一个包含netdefs.h文件的编译控制行,所以编译时的正文实际来自于两个文件:subnet.c和netdefs.h。 • 只要在生成subnet.o之后对这两个文件中任何一个又进行了更新,就要重新编译subnet.c以生成更新版本的subnet.o。这样的依赖关系与动作在Makefile中表示为: subnet.o: subnet.c netdefs.h cc -c subnet.c
综合起来,对于图11-2所示的软件系统,维护network程序的说明文件Makefile全部的依赖行和动作为:综合起来,对于图11-2所示的软件系统,维护network程序的说明文件Makefile全部的依赖行和动作为: network: subnet.o service.o cc -o network subnet.o servie.o subnet.o: subnet.c netdefs.h cc -c subnet.c service.o:service.c netdefs.h cc -c service.c
依赖说明可以嵌套,依赖行上的依赖文件可以是其他依赖行上的目标文件,这样就构成了系统内部各文件层次状的依赖关系。依赖说明可以嵌套,依赖行上的依赖文件可以是其他依赖行上的目标文件,这样就构成了系统内部各文件层次状的依赖关系。 • 如用户新改动了netdefs.h,以后执行命令make network,Make从“network:”这一依赖行开始自顶向下遍历以network为根的依赖关系树,由底向上考察每一个依赖行,并根据文件更新时间执行相应动作。 • 由于每一个模块都依赖于头文件netdefs.h,因此Make将依次执行每一组动作: cc -c service.c cc -c subnet.c cc -o network subnet.o service.o
如果用户仅仅修改了源程序service.c,Make只发现service.o和network这两个文件已过时,就执行了以下两个动作:如果用户仅仅修改了源程序service.c,Make只发现service.o和network这两个文件已过时,就执行了以下两个动作: cc -c service.c cc -o network subnet.o service.o • 实际上为了生成一个network的更新版本,Make将执行最少量的必须做的再编译工作。Make是最讲效率的。 • 在一个依赖行下可跟随任意多行的UNIX命令,直至下一个依赖行或空行为止。
12.4.2 Make的内部规则 • 上例给出的Make的说明比较繁琐,一般来说,service.o通常都依赖于service.c,对于其他的模块也是如此。 • 为了简化说明,Make提供了一组内部依赖规则,只要在Make的说明文件中没有对这些规则的另外说明,Make就使用这些规则来产生目标文件。表11-1列出了Make内部规则。