实验二 Linux编程基础
一、实验目的
熟悉Linux下使用C语言编程的方法,通过在编程中使用进程控制系统调用功能,直观理解Linux下进程并发执行情况。
二、实验题目
使用vi编辑C语言源程序;分析例子程序并通过man命令熟悉相关系统调用,编写进程并发执行的程序;使用gcc、make等工具进行编译;执行编译生成的执行文件并分析结果。
三、背景材料
因实验用的Linux环境以字符界面为主,而Linux的字符界面并没有广泛使用的集成开发环境,经典的C语言开发模式还是使用文本编辑软件编写源程序,然后使用gcc和make工具进行编译链接,生成可执行文件。在编辑源程序环节,可以使用不同的文本编辑工具,例如在图形用户界面下可以使用gedit或者kedit;字符界面下可以使用vi、emacs、nano等编辑器;甚至可以在其它环境下编辑,然后通过ftp上传。
(一)编辑器vi的使用
vi或vim是Linux系统上经典的文本编辑工具,在字符界面下,广泛用于系统管理、程序开发。本实验要求熟悉vi基本操作,可以使用vi进行文本编辑。 1、调用vi
最常用的命令模式是:vi filename 2、vi的三种模式
Command(命令)模式,用于输入命令; Insert(插入)模式,用于插入文本;
Visual(可视)模式,用于可视化的的高亮并选定正文。 3、文件的保存和退出
Command模式是vi的默认模式,如果处于其它模式时,要通过ESC键切换过来,接着再输入:号时,vi会在屏幕的最下方等待输入命令;
:w 保存; :w filename 另存为filename; :wq! 保存退出; :wq! filename 注:以filename为文件名保存后退出; :q! 不保存退出; :x 应该是保存并退出 ,功能和:wq!相同 4、光标移动;
按ESC进入Command模式后,可以用下面的一些键位来移动光标;
j 向下移动一行; k 向上移动一行; h 向左移动一个字符; l 向右移动一个字符; ctrl+b 向上移动一屏; ctrl+f 向下移动一屏; 向上箭头 向上移动; 向下箭头 向下移动; 向左箭头 向左移动; 向右箭头 向右移动; 对于 j、k、l和h键,还能在这些命令的前面加上数字,比如 3j,表示向下移动3行。 5、进入插入模式 i 在光标之前插入; a 在光标之后插入; I 在光标所在行的行首插入; A 在光标所在行的行末插入; o 在光标所在的行的上面插入一行; O 在光标所在的行的下面插入一行; s 删除光标后的一个字符,然后进入插入模式; S 删除光标所在的行,然后进入插入模式; 6、文本内容的删除 x 删除一个字符; #x 删除几个字符,#表示数字,比如3x; dw 删除一个单词; #dw 删除几个单词,#用数字表示,比如3dw表示删除三个单词; dd 删除一行; #dd 删除多个行,#代表数字,比如3dd 表示删除光标行及光标的下两行; d$ 删除光标到行尾的内容; J 清除光标所处的行与上一行之间的空格,把光标行和上一行接在一起; 7、可视模式
在最新的Linux发行版本中,vi提供了可视模式。按ESC键,然后按v就进入可视模式; 退出可视模式,还是用ESC键。可视模式提供了友好的选取文本范围,以高亮显示。 8、复制和粘帖的操作
复制:对于可视模式下选中的文本,可以按y进行复制;按yy可以复制当前光标所在行。此外,删除也带有剪切的意思,当我们删除文字时,相应内容会自动复制;
粘贴:可以把光标移动到某处,然后按p或shift+p键进行粘贴,其中,p 在光标之后粘帖;shift+p 在光标之前粘帖。 9、行号
可以在命令模式输入 :set number 来打开行号显示,输入 :set number 来关闭行号显示。 10、查找功能
在命令模式下,输入/或?就可以进行查找;
/SEARCH 注:正向查找,按n键把光标移动到下一个符合条件的地方; ?SEARCH 注:反向查找,按shift+n 键,把光标移动到下一个符合条件的 11、替换功能
按ESC键进入命令模式; :s /SEARCH/REPLACE/g 注:把当前光标所处的行中的SEARCH单词,替换成REPLACE,并把所有SEARCH高亮显示; :%s /SEARCH/REPLACE 注:把文档中所有SEARCH替换成REPLACE; :#,# s /SEARCH/REPLACE/g 注:#号表示数字,表示从多少行到多少行,把SEARCH替换成REPLACE; 注:命令中g表示全局查找,在指定的替换范围之外,也会把SEARCH高亮显示。
(二)gcc和make的使用
Linux下编辑好的C语言程序,需要使用gcc编译工具(Linux系统上一般也可以通过cc别名调用)进行编译连接,生成可执行文件。对于复杂的应用,可能把多个源程序分别编译,最后连接形成执行文件;为了有效管理这类项目,make代码维护工具可以用来定义一系列规则来管理编译连接过程。
【注】Linux下的可执行文件没有特别的后缀名约定,而是通过设置“可执行”属性来区分;另外,Linux默认的执行文件搜索路径也不包括当前目录,所以编译生成的执行文件需要显示指明文件路径来执行。 1、gcc的使用 (1) 简单使用方法
假设一个C语言源程序名为demo.c,根据该文件生成执行文件的最简单命令为: gcc hello.c
该命令将生成默认名字为a.out的执行文件;如果希望生成执行文件的名字不同,可以在命令中通过参数指定:
gcc -o hello hello.c
该命令将生成名字为demo的执行文件。 (2) 分解使用
实际上,gcc所做的工作包括预编译、编译、连接等多个环节,上述简单用法隐含了对这几个环节的调用,如果需要分开调用,可以通过不同的命令行参数指定:
gcc -E hello.c -o hello.i -E参数指定进行预编译,预处理的宏定义插入到hello.i中; gcc -c hello.i -o hello.o -c参数指定编译为目标代码 hello.o,也可以通过源文件直接生成(gcc -c hello.c);
gcc hello.o -o hello 将生成可执行文件,如果由多个源程序文件生成,一般需要在生成时指定多个目标代码模块,例如:gcc hello.o e1.o s2.o -o hello (3) 常用参数
gcc命令行的常用参数如下:
-c 通知GCC取消链接步骤,即编译源码并在最后生成目标文件; -Dmacro 定义指定的宏,使它能够通过源码中的#ifdef进行检验; -E 不经过编译预处理程序的输出而输送至标准输出;
-g3 获得有关调试程序的详细信息,它不能与-o选项联合使用; -Idirectory 在包含文件搜索路径的起点处添加指定目录;
-llibrary 提示链接程序在创建最终可执行文件时包含指定的库; -O、-O2、-O3 将优化状态打开,该选项不能与-g选项联合使用;
-S 要求编译程序生成来自源代码的汇编程序输出; -v 启动所有警报;
-Wall 在发生警报时取消编译操作,即将警报看作是错误; -Werror 在发生警报时取消编译操作,即把报警当作是错误; -w 禁止所有的报警。 2、make的使用 (1) make工具简介
make是一种代码维护工具,在大中型项目中,根据程序各个模块的更新情况,自动的维护和生成目标。能很好的做到“不多、不重、不漏”:
不多:只更新那些需要更新的文件,而不动那些并不过时的东西;
不重:是指当make对某个文件进行更新时,即使有很多文件过时,也将只更新一
次;
不漏:是指他不会漏下任何应该更新的文件。
make在使用时有一系列的规则,将根据这些规则来解释其配置脚本,以达到设计目的。配置脚本的缺省名是makefile或Makefile,也可在命令行指定其它文件名。使用形式为:
make [option] [macrodef] [target]
option指出make的工作行为,具体参数可参考man手册页。 (2) makefile文件编写
make工具使用的关键是编写高效、简洁、正确的makefile,这需要学习和实践。虽然make工具的使用有较高的门槛,但是一旦掌握,其带来的灵活和便捷远远超过一般IDE所能提供的管理功能。
实际makefile文件编写涉及的知识很多,有专著专门最次讲解。此处只是介绍概貌,借助一个简单的例子来说明:假设一个项目hello,依赖于hello.o、e1.o、e2.o,而hello.o依赖于hello.c、hello.h,e1.o依赖于e1.c、hello.h,e2.o依赖于e2.c;这样形成一棵依赖关系树,父节点依赖于子节点。使用make进行维护时,会对这棵树进行一次遍历,如果发现子节点形成的时间晚于父节点形成的时间便开始调用makefile中指定的命令进行维护。针对此例子的makefile示例如下:
hello: hello.o e1.o e2.o
gcc -o hello hello.o e1.o e2.o main.o: hello.c hello.h gcc -c hello.c e1.o: e1.c hello.h gcc -c e1.c hello.h e2.o: e2.c gcc -c e2.c
makefile放置在与项目源程序文件相同目录中,在该目录中执行make hello或者make命令(命令行未target时会默认使用makefile的第一个target),将会根据makefile中定义的依赖关系依次执行编译、连接各个命令,生成hello执行文件。
实际项目的makefile因为会使用大量的宏、条件变量以及缺省规则,对初学者而言较难理解。对于一般的开源项目,在编写makefile时,target的命名都会遵循一些预定俗称的规则,常见的用法包括:
make all 编译项目中所有实际目标 make install 进行安装
make clean 清除所有中间文件
(3) 缺省规则和简单用法
make的缺省规则是系统或者自定义的一些规则,在定义时不指出全名,而是根据文件扩展名定义一类依赖关系和相关命令。例如,与c语言相关的缺省规则包括:
.c: $(CC) $(FLAGS) -o $@ $< .c .o: $(CC) $(FLAGS) -c $@ $<
两条规则的含义为:对于.c扩展名的文件,调用编译器(通过环境变量CC定义,默认为gcc)生成与.c文件对应名字的执行文件;对于.o、.c扩展名配对的情况,调用编译器进行编译,生成与.c文件对应名字的.o目标文件。
根据这些缺省规则,可以在简单情况下不编写makefile使用make工具。例如,对于根据hello.c生成hello执行文件的情况,可以直接使用命令
make hello
效果等同于:gcc -o hello hello.c
(三)需要用到的系统调用
实验要求在例子程序基础上编程验证Linux下进程并发情况,需要用到的系统调用和库函数在下面列出,详细的使用方法说明通过“man 2 系统调用名”或者“man 3 函数名”命令获取。
fork() 创建一个子进程,通过返回值区分是在父进程还是子进程中执行; wait() 等待子进程执行完成; sleep() 导致调用进程睡眠指定秒数; getpid() 获取当前进程id; getppid() 获取父进程id。
四、实验内容
为便于掌握Linux环境下进行C语言编程的基本技能,本实验提供源程序例子,实验内容要在给出的例子程序基础上,根据要求进行修改,对执行结果进行分析。 1、子进程对存储空间的复制 (1) 使用文本编辑器输入源程序
输入如下源程序:
#include #include #include int main(void) {
int x, i;
printf(\"Input a initial value for i: \"); scanf(\"%d\
while((x=fork())==-1); if(x==0) /* child run */ {
printf(\"When child runs, i=%d\\n\ printf(\"Input a value in child: \"); scanf(\"%d\ printf(\"i=%d\\n\ } else /* parent run */ {
wait();
printf(\"After child runs, in parent, i=%d\\n\ } }
(2) 预测程序的执行结果
阅读程序,根据自己的理解,预期程序执行后的结果。 (3) 实际执行结果分析
编译生成执行文件,执行后记录结果,说明与预期的结果是否一致。分析为什么是这样的结果。
2、父子进程执行过程分析 (1) 按照给定框架编程
按照如下程序框架,完成源程序编写:
// ①
pid=fork(); // ② if(pid==0) {
sleep(3);
printf(\"Child: pid=%d, ppid=%d\\n\ }
else {
printf(\"Parent: Child=%d, pid=%d, ppid=%d\\n\ wait();
printf(\"After Child ends.\\n\"); }
printf(\"In which process?\\n\"); // ③
(2) 预测程序的执行结果
阅读程序,根据自己的理解,预期程序执行后的结果。 (3) 实际执行结果分析
编译生成执行文件,执行后记录结果,说明与预期的结果是否一致。分析为什么是这样的结果。
(4) 修改程序并分析执行结果
把程序框架中最后一句输出语句(位置③处)分别移至位置①和②处,预期输出结果是什么?实际执行结果如何?分析原因。 (5) 修改程序验证父子进程关系
修改程序,使父进程先执行完成,验证子进程是否会一起终止?如果不是,前后子进程的父进程号是否变化?记录并分析结果。
五、实验报告书写要求
应在实验报告中说明如下事项: 1、所使用的文本编辑器; 2、编译生成执行文件的命令;
3、相关程序的名称及存储位置,以便指导教师抽查;
4、实验内容1的结果:包括预期结果和实际执行结果,以及结果分析;
5、实验内容2的结果:包括预期结果和实际执行结果,对结果的分析;按照要求进行修改后的预期结果、实际结果及分析。