实验目的
(1) 熟悉Linux下的基本操作,学会使用各种Shell命令去操作 Linux,对Linux
有一个感性认识。
(2) 熟悉使用Linux的编程接口,即系统调用。
(3) 学会使用编辑器编辑简单的C语言程序,并能对其编译和调试。 实验内容
(1) 使用Login,Logout等命令实现系统的访问
(2) 以root用户身份登陆,并使用“ls”,“cat”“cd”等命令来实现基本的文
件操作并观察Linux文件系统的特点;
(3) 使用编辑器编写一个C程序,并用gcc命令进行编译和链接,并用a.out来
进行输出结果。 实验指导
一、登陆Linux
Linux 进入,再选择Ubuntu登陆窗口,进入Ubuntu图形桌面环境。 开机,选择 二、熟悉Ubuntu图形桌面环境
桌面包含上方的菜单栏和下方的任务栏。菜单栏包含“应用程序菜单”、“位置”、“系统”。
通过主菜单可访问应用程序。 (1) “应用程序”菜单
“应用程序”菜单包含“办公”、“附件”、“互联网”、“图形”、“游戏”等。 “办公”包含了文字处理及电子表格等应用程序。
“附件”下包含了“搜索工具”、“计算器”、“文本编辑器”和“终端”等应用程序。
(2)“位置”菜单
“位置”菜单包含“主文件夹”、“桌面”等信息。
(3)“系统”菜单
1 / 11
“系统”菜单包含“首选项”和“系统管理”等信息。
(4)启动终端模拟器
GNOME终端模拟器用一个窗口来模拟字符终端的行为。终端常常被称为命令行或者
shell,Linux 中绝大部分工作都可以用命令行完成。要启动一个终端,可以选择 应用程序 → 附件 → 终端。
三、练习常用的Shell命令。
当用户登录到字符界面系统或使用终端模拟窗口时,就是在和称为shell的命令解释程序进行通信。当用户在键盘上输入一条命令时,shell程序将对命令进行解释并完成相应的动作。这种动作可能是执行用户的应用程序,或者是调用一个编辑器、GNU/Linux实用程序或其他标准程序,或者是一条错误信息,告诉用户输入了错误的命令。 1.目录操作
mkdir abc 创建一个目录abc cd abc 将工作目录改变到abc cd 改变当前目录到主目录 ls 列出当前目录的内容
ls -l 输出当前目录内容的长列表,每个目录或文件占一行 pwd 显示当前目录的全路径 2.文件显示实用程序
cat mx.c 显示mx.c文件内容 more mx.c 分屏显示mx.c内容 tail mx.c 显示文件后几行 cat file1 file2 连接file1 和file2 head 显示文件的开始10行
wc 统计文件中的行数、单词数和字符数 od 文件 查看非文本文件 3.文件管理实用程序
cp file1 file2 将文件1复制到文件2 mv file1 file2 将文件重命名为file2 rm 删除文件
rm -i 请求用户确认删除 4.数据操作实用程序
tty 显示当前终端的路径和文件名 who 显示当前登录用户的列表 sort 显示文件中的行的排序结果 spell 检查文件中的拼写错误
5.其他实用程序
date 输出系统日期和时间
cal 显示本月的日历。cal 2002 显示2002年的日历 clear 清除终端屏幕
2 / 11
history 显示你以前执行过的命令的列表
man 显示实用程序的有用信息,并提供该实用程序的基本用法 echo 读取参数并把它写到输出 四、目录和文件系统
Linux 和 Unix 文件系统被组织成一个有层次的树形结构。文件系统的最上层是 /,或称为 根目录。在 Unix 和 Linux 的设计理念中,一切皆为文件——包括硬盘、分区和可插拔介质。这就意味着所有其它文件和目录(包括其它硬盘和分区)都位于根目录中。 例如:/home/jebediah/cheeses.odt 给出了正确的完整路径,它指向 cheeses.odt 文件,而该文件位于 jebediah 目录下,该目录又位于 home 目录,最後,home 目录又位于根(/) 目录下。 在根 (/) 目录下,有一组重要的系统目录,在大部分 Linux 发行版里都通用。直接位于根 (/) 目录下的常见目录列表如下:
• • • • • • • • • • •
/bin - 重要的二进制 (binary) 应用程序 /boot - 启动 (boot) 配置文件 /dev - 设备 (device) 文件 /etc - 配置文件、启动脚本等 (etc) /home - 本地用户主 (home) 目录 /lib - 系统库 (libraries) 文件
/lost+found - 在根 (/) 目录下提供一个遗失+查找(lost+found) 系统 /media - 挂载可移动介质 (media),诸如 CD、数码相机等 /mnt - 挂载 (mounted) 文件系统
/opt - 提供一个供可选的 (optional) 应用程序安装目录
/proc - 特殊的动态目录,用以维护系统信息和状态,包括当前运行中进程 (processes) 信息。
• • • • •
/root - root (root) 用户主文件夹,读作“slash-root” /sbin - 重要的系统二进制 (system binaries) 文件 /sys - 系统 (system) 文件 /tmp - 临时(temporary)文件
/usr - 包含绝大部分所有用户(users)都能访问的应用程序和文件
/var - 经常变化的(variable)文件,诸如日志或数据库等 五、熟悉gcc编译器
GNU/Linux中通常使用的C编译器是GNU gcc。编译器把源程序编译生成目标代码的任务分为以下4步:
a. 预处理,把预处理命令扫描处理完毕;
b. 编译,把预处理后的结果编译成汇编或者目标模块;
3 / 11
c. 汇编,把编译出来的结果汇编成具体CPU上的目标代码模块; d. 连接,把多个目标代码模块连接生成一个大的目标模块;
1.使用语法:
gcc [ option | ]...
其中 option 为 gcc 使用时的选项,而 为 gcc要处理的文件。
2.GCC选项
GCC的选项有很多类,这类选项控制着GCC程序的运行,以达到特定的编译目的。 –o file (常用)
指明输出文件名是file。 –g
调试开关(Debugging Options)
把调试开关打开,让编译的目标文件有调试信息。 六、掌握Ubuntu下C程序编辑运行过程(重点) Ubuntu下编写C程序要经过以下几个步骤:
⑴启动常用的编辑器,键入C源程序代码。 例如,点击应用程序/附件/文本编辑器,进入编辑环境,输入C源程序,保存并命名为hello.c # include printf(“Hello world!\\n”); } ⑵编译源程序 点击应用程序/附件/终端,进入命令行。用gcc编译器对C源程序进行编译,以生成一个可执行文件。方法: gcc -o hello.out hello.c ↙ ⑶运行可执行文件 ·/hello.out ↙ 注:命令行中 -o选项表示要求编译器输出可执行文件名为hello.out文件,hello.c是源程序文件。 实验二 进程管理 1. 实验目的 ⑴ 加深对进程概念的理解,明确进程和程序的区别; ⑵ 进一步认识并发执行的实质; ⑶ 分析进程争用资源的现象,学习解决进程互斥的方法; 4 / 11 2. 实验准备 ⑴ 阅读Linux的sched.h源码文件,加深对进程管理的理解。 ⑵ 阅读Linux的fork.h源码文件,分析进程的创建过程。 3. 实验内容 (1) 进程的创建 编写一段程序,使用系统调用fork ( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程显示字符“b”和字符“c”。试观察记录屏幕上的显示结果,并分析原因。 (2) 进程的控制 修改已编写的程序,将每个进程输出一个字符改为每个进程输出一句话,再观察程序执行时屏幕上出现的现象,并分析原因。 如果在程序中使用系统调用lockf ( )来给每一个进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。 (3) 编写程序创建进程树(选做) 编写程序创建进程树如图1和图2所示,在每个进程中显示当前进程识别码和父进程识别码。 a b c d 父进程 a b d e 子进程 c 图1 进程树 图2 进程树 4. 实验指导 1) 进程 LINUX 中,进程既是一个独立拥有资源的基本单位,又是一个独立调度的基本单位。一个进程实体由若干个区(段)组成,包括程序区、数据区、栈区、共享存储区等。每个区又分为若干页,每个进程配置有唯一的进程控制块 PCB,用于控制和管理进程。 PCB 的数据结构如下: (A) 进程表项(Process Table Entry)。包括一些最常用的核心数据: 进程标识符 PID、用户标识符 UID、进程状态、事件描述符、进程和 U 区在内存或外存的地址、软中断信号、计时域、进程的大小、偏置值 nice、指向就绪队列中下一个PCB 5 / 11 的指针 P_Link、指向 U 区进程正文、数据及栈在内存区域的指针。 (B) U 区(U Area)。用于存放进程表项的一些扩充信息。 每一个进程都有一个私用的 U 区,其中含有:进程表项指针、真正用户标识符uruid(read user ID)、有效用户标识符 ueuid(effective user ID)、用户文件描述符表、计时器、内部 I/O 参数、限制字段、差错字段、返回值、信号处理数组。由于 LINUX 系统采用段页式存储管理,为了把段的起始虚地址变换为段在系统中的物理地址,便于实现区的共享。 (C) 系统区表项。以存放各个段在物理存储器中的位置等信息。 系统把一个进程的虚地址空间划分为若干个连续的逻辑区,有正文区、数据区、栈区等。这些区是可被共享和保护的独立实体,多个进程可共享一个区。为了对区进行管理,核心中设置一个系统区表,各表项中记录了以下有关描述活动区的信息:区的类型和大小、区的状态、区在物理存储器中的位置、引用计数、指向文件索引结点的指针。 (D) 进程区表 系统为每个进程配置了一张进程区表。表中,每一项记录一个区的起始虚地址及指向系统区表中对应的区表项。核心通过查找进程区表和系统区表,便可将区的逻辑地址变换为物理地址。 2) 进程映像 LINUX 系统中,进程是进程映像的执行过程,也就是正在执行的进程实体。它由三部分组成: (A) 用户级上下文。主要成分是用户程序; (B) 寄存器上下文。由 CPU 中的一些寄存器的内容组成,如 PC,PSW,SP 及通用寄存器等; (C) 系统级上下文。包括 OS 为管理进程所用的信息,有静态和动态之分。 3) 所涉及的系统调用 (A) fork( ),创建一个新进程,并复制进程,使父子进程内容几乎相同。 系统调用格式:pid = fork( ) 参数定义:int fork( ) fork( )返回值意义如下: 0:在子进程中,pid 变量保存的 fork( )返回值为 0,表示当前进程是子进程。 >0:在父进程中,p id 变量保存的 fork( )返回值为子进程的 id 值( 进程唯一标 识符)。 6 / 11 -1:创建失败。 如果 fork( )调用成功,它向父进程返回子进程的 PID,并向子进程返回 0,即fork( )被调用了一次,但返回了两次。此时 OS 在内存中建立一个新进程,所建的新进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。核心为 fork( )完成以下操作: a) 为新进程分配一进程表项和进程标识符 进入 fork( )后,核心检查系统是否有足够的资源来建立一个新进程。若资源不足,则fork( )系统调用失败;否则,核心为新进程分配一进程表项和唯一的进程标识符。 b) 检查同时运行的进程数目 超过预先规定的最大数目时,fork( )系统调用失败。 c) 拷贝进程表项中的数据 将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为“创建”状态。 d) 子进程继承父进程的所有文件 对父进程当前目录和所有已打开的文件表项中的引用计数加 1。 e) 为子进程创建进程上、下文 进程创建结束,设子进程状态为“内存中就绪”并返回子进程的标识符。 f) 子进程执行 虽然父进程与子进程程序完全相同,但 每个进程都有自己的程序计数器 PC(注意子进程的 PC 开始位置),然后根据 pid 变量保存的 fork( )返回值的不同,执行了不同的分支语句。 (B) wait( ) 等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在 wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。 系统调用格式: int wait(status) int *status; 其中,status 是用户空间的地址。它的低 8 位反应子进程状态,为0 表示子进程正常 7 / 11 结束,非 0 则表示出现了各种各样的问题;高 8 位则带回了 exit( )的返回值。exit( )返回值由系统给出。 核心对 wait( )作以下处理: a) 首先查找调用进程是否有子进程,若无,则返回出错码; b) 若找到一处于“僵死状态”的子进程,则将子进程的执行时间加到父进程的执行时间上,并释放子进程的进程表项; c) 若未找到处于“僵死状态”的子进程,则调用进程便在可被中断的优先级上睡眠,等待其子进程发来软中断信号时被唤醒。 (C) exit( ) 终止进程的执行。系统调用格式: void exit(status) int status; 其中,status 是返回给父进程的一个整数,以备查考。 为了及时回收进程所占用的资源并减少父进程的干预,LINUX/LINUX 利用 exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条 exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。如果调用进程在执行 exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。核心须为 exit( )完成以下操作: a) 关闭软中断 b) 回收资源 c) 写记帐信息 d) 置进程为“僵死状态” (D) lockf(): 用作锁定文件的某些段或者整个文件,本函数适用的头文件为: #include 4) 参考程序 8 / 11 ⑴ 进程的创建<程序> #include int p1, p2; while ((p1=fork ( ))= = -1); /*创建子进程*/ if (p1= =0) /*子进程创建成功*/ putchar (‘b’); else { while ((p2=fork ( )) = = -1); /*创建另一个子进程*/ if (p2= =0) /*子进程创建成功*/ putchar (‘c’); else putchar (‘a’); /*父进程执行*/ } } <运行结果> bca(有时会出现bac等) <分析> 从进程并发执行来看,输出bac,acb等情况都有可能。 <原因> fork()创建进程所需的时间虽然可能多于输出一个字符的时间,上面的三个进程没有同步措施,所以父进程与子进程的输出内容会叠加在一起。输出次序带有随机性。。 ⑵ 进程的控制 <程序1> #include void main() { int p1,p2,i; if(p1=fork()) { if((p2=fork())==0) { for(i=0;i<5;i++) printf(\"daughter %d\\n\ exit(0); } 9 / 11 } } else { for(i=0;i<5;i++) printf(\"son %d\\n\ exit(0); /*向父进程信号0且该进程出*/ } <运行结果> for(i=0;i<5;i++) printf(\"parent%d\\n\ wait(0); /* 保证在子进程终止前,父进程不会终止*/ wait(0); /* 保证在子进程终止前,父进程不会终止*/ exit(0); <分析> 由于函数printf ( ) 输出的字符串之间不会被中断,因此,字符串内部的字符顺序输出时不变。但是,由于进程并发执行时的调度顺序和父子进程的抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化,这与打印单字符的结果相同。 <程序2> #include void main() { int p1,p2,i; if(p1=fork()) { if((p2=fork())==0) 10 / 11 { lockf(1,1,0); for(i=0;i<5;i++) printf(\"daughter %d\\n\ lockf(1,0,0); exit(0); } lockf(1,1,0); for(i=0;i<5;i++) printf(\"parent%d\\n\ lockf(1,0,0); wait(0); /* 保证在子进程终止前,父进程不会终止*/ wait(0); /* 保证在子进程终止前,父进程不会终止*/ exit(0); } else { lockf(1,1,0); for(i=0;i<5;i++) printf(\"son %d\\n\ lockf(1,0,0); exit(0); /*向父进程信号0且该进程出*/ } }<运行结果> 大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。 <分析> 因为上述程序执行时,lockf(1,1,0)锁定标准输出设备,lockf(1,0,0)解锁标准输出设备,在lockf(1,1,0)与lockf(1,0,0)中间的for循环输出不会被中断,加锁与不加锁效果不相同。 11 / 11 因篇幅问题不能全部显示,请点此查看更多更全内容