640 likes | 781 Views
操作系统实验辅导. 2011. 内容. Linux 实验环境 系统调用 其他问题 实验讲解. Linux 实验环境. 系统常用命令 编辑器 编译器 调试器. Linux 系统常用命令. 显示目录文件 ls 执行格式: ls [-atFlgR] [name] (name 可为文件或目录名称 ) 例: ls 显示出当前目录下的文件 ls -a 显示出包含隐藏文件的所有文件 ls -t 按照文件最后修改时间显示文件
E N D
操作系统实验辅导 2011
内容 • Linux实验环境 • 系统调用 • 其他问题 • 实验讲解
Linux实验环境 • 系统常用命令 • 编辑器 • 编译器 • 调试器
Linux系统常用命令 • 显示目录文件 ls • 执行格式: ls [-atFlgR] [name] (name可为文件或目录名称) 例: ls 显示出当前目录下的文件 ls -a 显示出包含隐藏文件的所有文件 ls -t 按照文件最后修改时间显示文件 ls -F 显示出当前目录下的文件及其类型 ls -l 显示目录下所有文件的许可权、拥有者、文件大 小、修改时间及名称 ls -lg 同上 ls -R 显示出该目录及其子目录下的文件
Linux常用命令 • 建新目录 mkdir • 执行格式: mkdir directory-name 例: mkdir dir1 新建一个名字为dir1的目录
Linux常用命令 • 删除目录 rmdir • 执行格式: rmdir directory-name 或 rm directory-name 例: rmdir dir1 删除目录dir1,但它必须是空目录,否则无法删除 rm -r dir1 删除目录dir1及其下所有文件及子目录 rm -rf dir1 不管是否空目录,统统删除,而且不给出提示,使用时要小心
Linux常用命令 • 改变工作目录位置 cd • 执行格式: cd [name] 例: cd 改变目录位置至用户login时的working directory cd dir1 改变目录位置,至dir1目录 cd ~user 改变目录位置,至用户的working directory cd .. 改变目录位置,至当前目录的上层目录 cd ../user 改变目录位置,至上一级目录下的user目录 cd /dir-name1/dir-name2 改变目录位置,至绝对路径 cd - 回到进入当前目录前的上一个目录
Linux常用命令 • 显示当前所在目录 pwd • 执行格式: pwd
Linux常用命令 删除文件 rm 执行格式: rm filename 例: rm file 删除名字为file的文件
Linux常用命令 • 复制文件 cp • 执行格式: cp [-r] source destination 例: cp file1 file2 将file1复制成file2 cp file1 dir1 将file1复制到目录dir1 cp /tmp/file1 将file1复制到当前目录 cp /tmp/file1 file2 将file1 复制到当前目录名为 file2 cp –r dir1 dir2 (recursive copy)复制整个目 录。
Linux常用命令 • 移动或更改文件、目录名称 mv • 执行格式: mv source destination 例: mv file1 file2 将文件file1,更名为file2 mv file1 dir1 将文件file1,移到目录dir1 下 mv dir1 dir2 将目录dir1,更名为dir2
Linux常用命令 • 查看系统目前的进程 ps • 执行格式: ps [-aux] 例: ps 或ps -x 查看系统中属于自己的process ps -au 查看系统中所有使用者的process ps -aux 查看系统中包含系统内部及 所有使用者 的process ps -aux|grep apache 找出系统中运行的所有名称 中带有“apache”串的 process
Linux常用命令 • 结束或终止进程 kill • 执行格式: kill [-9] PID (PID为利用ps命令所查出的process ID) 例: kill 456 kill -9 456 终止process ID 为456的 process
Linux常用命令 • 命令在线帮助 man • 执行格式: man command 例: man ls 查询ls这个指令的用法
vi编辑器的使用 • vi的全称是visual editor,它是在Unix/Linux 上被广泛使用的中英文编辑器。 • 启动vi vi 进入vi编辑器界面,没有为 所编辑的文件命名 vi filename 进入vi编辑器界面,并将所 编辑的文件命名为filename
vi编辑器的使用 vi提供三种工作模式: • 输入编辑模式 支持:输入文本信息 • 命令编辑模式 支持:进行删除、修改等操作信息 • 命令模式 支持:进行存盘、退出等操作 注意:使用者进入vi后,即处在命令模式下,要输入信息,则要切换至输入模式。
vi编辑器的使用 命令编辑模式下:
vi编辑器的使用 在命令模式下:
gedit编辑器的使用 • gedit是Unix/Linux系统下的另一种中英文文本编辑器,支持窗口模式,更加方便快捷。 • 启动gedit: gedit 进入gedit编辑器界面, 没有为所编辑的文件命名 gedit filename 进入gedit编辑器界面, 并将所编辑的文件命名为 filename
GNU C编译器的使用 • LINUX上可用的C编译器是GNU C编译器(GCC),一般的Unix下使用的是CC编译器 • 通常后跟一些选项和文件名来使用GCC编译器。基本用法如下: gcc [options] [filenames] • 通过命令行选项指定编译过程中的具体操作
GNU C编译器的使用 GCC常用选项 • GCC有超过100个的编译选项可用,这些选项中的许多可能永远都不会用到,但一些主要的选项将会频繁使用。很多的GCC选项包括一个以上的字符,因此必须为每个选项指定各自的连字符,并且就像大多数LINUX 命令一样不能在一个单独的连字符后跟一组选项。例如,下面的命令是不同的: gcc -p -g test.c gcc -p test.c 第一条命令告诉GCC编译test.c时为prof命令建立剖析(profile)信息并且把调试信息加入到可执行文件里。第二条命令告诉GCC只为gprof命令建立剖析信息。
GNU C编译器的使用 GCC常用选项 • 当不用任何选项编译一个程序时,GCC将建立(假定编译成功)一个名为a.out的可执行文件。例如, gcc test.c 编译成功后,当前目录下就产生了一个a.out文件。 • 也可用-o选项来为即将产生的可执行文件指定一个文件名来代替a.out。例如: gcc –o count count.c 此时得到的可执行文件就不再是a.out,而是count。
GNU C编译器的使用 执行文件 • 格式: ./可执行文件名 例:./a.out ./count
gdb调试工具的使用 • LINUX包含了一个叫gdb的GNU调试程序。gdb是一个用来调试C和C++程序的强有力调试器。它使你能在程序运行时观察程序的内部结构和内存的使用情况。它具有以下一些功能: ·监视程序中变量的值; ·设置断点以使程序在指定的代码行上停止执行; ·一行行的执行代码。
gdb调试工具的使用 • 为了使gdb正常工作,必须使你的程序在编译时包含调试信息。调试信息里包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb利用这些信息使源代码和机器码相关联。 • 在编译时用 –g 选项打开调试选项。
gdb调试工具的使用 • 调试命令
gdb调试工具的使用 应用举例 (1)设有一源程序 test.c (2)编译,gcc -ggdb –o greet greet.c (3)gdb greet ,出现提示符(gdb),此时可在提示符下输入gdb的命令了,如: (gdb)run (gdb)list (4)退出调试状态,返回系统提示符下, (gdb)quit
实验中用到的系统调用 • 字面上讲,系统调用(也称为“syscall”)就是一条类似于“add”或者“jump”的指令。从更高的层面上讲,系统调用是用户级程序要求操作系统为它做某些事情的途径。 • 首先,用户程序为系统调用设置参数。其中一个参数是系统调用编号(稍后对此进行详述)。注意,所有这些都是由库函数自动完成的,除非您是使用汇编编程。参数设置完成后,程序执行“系统调用”指令。这个指令会导致一个异常:产生一个事件,这个事件会致使处理器跳转到一个新的地址,并开始执行那里的代码。
实验中用到的系统调用 举例说明: • fork( ) 创建进程 stdio.h • wait( )等待子进程运行结束 stdio.h • exit( )终止进程的执行 stdio.h • lockf(files,function,size) 锁定文件 unistd.h • kill( ) 发送信号 signal.h • signal( ) 预置对信号的处理方式 signal.h 注意:程序中用到系统调用的时候,要包含相关的头文件 如: #include <signal.h>
实验中用到的系统调用 fork( ) #include <unistd.h> pid_t fork(void); • 创建一个新进程。 • 系统调用格式: pid=fork( ) • 参数定义:int fork( ) • fork( )返回值意义如下: 0:在子进程中,pid变量保存的fork( )返回值为0,表示当前进程是子进程。 >0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。 -1:创建失败。
实验中用到的系统调用 • 如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。
实验中用到的系统调用 核心为fork( )完成以下操作: • 为新进程分配一进程表项和进程标识符 进入fork( )后,核心检查系统是否有足够的资源来建立一个新进程。若资源不足,则fork( )系统调用失败;否则,核心为新进程分配一进程表项和唯一的进程标识符。 • 检查同时运行的进程数目 超过预先规定的最大数目时,fork( )系统调用失败。 • 拷贝进程表项中的数据 将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为“创建”状态。 • 子进程继承父进程的所有文件 对父进程当前目录和所有已打开的文件表项中的引用计数加1。 • 为子进程创建进程上、下文 进程创建结束,设子进程状态为“内存中就绪”并返回子进程的标识符。 • 子进程执行 虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置),然后根据pid变量保存的fork( )返回值的不同,执行了不同的分支语句。
实验中用到的系统调用 wait( ) #include <sys/wait.h> • 等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。 • 系统调用格式: int wait(status) int *status; • 其中,status是用户空间的地址。它的低8位反应子进程状态,为0表示子进程正常结束,非0则表示出现了各种各样的问题;高8位则带回了exit( )的返回值。exit( )返回值由系统给出。
实验中用到的系统调用 核心对wait( )作以下处理: • 首先查找调用进程是否有子进程,若无,则返回出错码; • 若找到一处于“僵死状态”的子进程,则将子进程的执行时间加到父进程的执行时间上,并释放子进程的进程表项; • 若未找到处于“僵死状态”的子进程,则调用进程便在可被中断的优先级上睡眠,等待其子进程发来软中断信号时被唤醒。
实验中用到的系统调用 exit( ) • 终止进程的执行。 • 系统调用格式: void exit(status) int status; • 其中,status是返回给父进程的一个整数,以备查考。 • 为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。 • 如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。
实验中用到的系统调用 核心须为exit( )完成以下操作: • 关闭软中断 • 回收资源 • 写记帐信息 • 置进程为“僵死状态”
实验中用到的系统调用 lockf(files,function,size) • 用锁定文件的某些段或者整个文件。 • 本函数的头文件为 #include "unistd.h" • 系统调用格式: int lockf(files,function,size) int files,function; long size; • 其中:files是文件描述符;function是锁定和解锁:1表示锁定,0表示解锁。size是锁定或解锁的字节数,为0,表示从文件的当前位置到文件尾。
实验中用到的系统调用 kill( ) #include <signal.h> • 系统调用格式 int kill(pid,sig) int pid,sig; • 其中,pid是一个或一组进程的标识符,参数sig是要发送的软中断信号。 (1)pid>0时,核心将信号发送给进程pid。 (2)pid=0时,核心将信号发送给与发送进程同组 的所有进程。 (3)pid=-1时,核心将信号发送给所有用户标识符 真正等于发送进程的有效用户标识号的进程。
实验中用到的系统调用 signal( ) • 预置对信号的处理方式,允许调用进程控制软中断信号。 • 系统调用格式 signal(sig,function) int sig; void (*func) ( ) • 其中sig用于指定信号的类型,sig为0则表示没有收到任何信号,其它信号类型见下页表格
实验一 进程创建 • 内容:编写一段程序,使用系统调用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止: Child process 1 is killed by parent! Child process 2 is killed by parent! 父进程等待两个子进程终止后,输出以下信息后终止: Parent process is killed! • 实验要求: (1) 运行程序并分析结果。 (2) 在程序中什么位置用了系统调用wait()和exit(),为什么?
实验一 进程创建: • 知识要点: 进程的创建;父进程和子进程的关系;信号的种类;自定义信号处理函数;信号的发送;进程终止。 • 系统调用: fork, signal, kill, wait, exit sleep, pause • 示例程序:
实验一 进程创建: • 进程创建过程 • 子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。 • fork该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程I D。 • 将子进程I D返回给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以获得其所有子进程的进程I D。f o r k使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用g e t p p i d以获得其父进程的进程I D。 • 父进程和子进程的关系:进程树结构 (pstree命令)
实验一 进程创建: • fork系统调用: #include <sys/types.h> #include <unistd.h> pid_t fork(void); 返回:子进程中为0,父进程中为子进程I D,出错为-1 • 例子 apue8-1.c $ a . o u t a write to stdout before fork pid = 430, glob = 7, var = 89 子 进 程 的变量值改变了 pid = 429, glob = 6, var = 88 父 进 程 的变量值没有改变 • 说明:子进程复制了父进程数据空间、堆和栈。注意,这是子进程所拥有的拷贝。父、子进程并不共享这些存储空间部分。(如果正文段是只读的,则父、子进程共享正文段。)
实验一 进程创建: • 进一步测试,当将标准输出重新定向到一个文件时: $ a.out > temp.out $ cat temp.out a write to stdout before fork pid = 432, glob = 7, var = 89 before fork pid = 431, glob = 6, var = 88 • 说明: • 子进程继承父进程重定向之后的输出文件; • 得到p r i n t f输出行两次。其原因是,如果标准输出连到终端设备则它是行缓存的,否则它是全缓存的。 • 在f o r k之前调用了p r i n t f一次,但当调用f o r k时,该行数据仍在缓存中,然后在父进程数据空间复制到子进程中时,该缓存数据也被复制到子进程中。于是那时父、子进程各自有了带该行内容的缓存。在e x i t之前的第二个p r i n t f将其数据添加到现存的缓存中。当每个进程终止时,其缓存中的内容被写到相应文件中。(两次)
在重新定向父进程的标准输出时,子进程的标准输出也被重新定向。实际上, f o r k的一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程每个相同的打开描述符共享一个文件表项。
实验一 进程创建: • f o r k有两种用法: • (1) 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待委托者的服务请求。当这种请求到达时,父进程调用f o r k,使子进程处理此请求。父进程则继续等待下一个服务请求。 • (2) 一个进程要执行一个不同的程序。这对s h e l l是常见的情况。在这种情况下,子进程在从f o r k返回后立即调用e x e c。 • 某些操作系统将( 2 )中的两个操作( f o r k之后执行e x e c )组合成一个,并称其为s p a w n。U N I X将这两个操作分开,因为在很多场合需要单独使用f o r k,其后并不跟随e x e c。另外,将这两个操作分开,使得子进程在f o r k和e x e c之间可以更改自己的属性。如I / O重新定向、用户I D、信号排列等。
进程终止 进程有三种正常终止法及两种异常终止法。 • (1) 正常终止: • (a) 在m a i n函数内执行r e t u r n语句。如在7 . 3节中所述,这等效于调用e x i t。 • (b) 调用e x i t函数。此函数由ANSI C定义,其操作包括调用各终止处理程序,然后关闭所有标准I / O流等。 (c) 调用_ e x i t系统调用函数。此函数由e x i t调用,它处理U N I X特定的细节。_ e x i t是由P O S I X . 1说明的。 • (2) 异常终止: • (a) 调用a b o r t。它产生S I G A B RT信号,所以是下一种异常终止的一种特例。 • (b) 当进程接收到某个信号时。(第1 0章将较详细地说明信号。)进程本身(例如调用a b o r t函数)、其他进程和内核都能产生传送到某一进程的信号。例如,进程越出其地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。
#include <stdlib.h> void exit(int status); • 终止进程能够通知其父进程它是如何终止的。对于e x i t和_ e x i t,这是依靠传递给它们的退出状态( exit status)参数来实现的。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)。 • 在任意一种情况下,该终止进程的父进程都能用w a i t或w a i t p i d函数(在下一节说明)取得其终止状态。 • 但是如果父进程在子进程之前终止,则将如何呢?其回答是对于其父进程已经终止的所有进程,它们的父进程都改变为i n i t进程。我们称这些进程由i n i t进程领养。