《操作系统原理及应用》实验指导书
程序中调用system函数时,用字符串参数传递一个shell命令,并执行 函数原型: #include int system(char *string)
调用举例:system(“ps -af”);
实验指导
1、列出当前终端上启动的所有进程。试观察屏幕上的显示结果。 [root@localhost root]# ps -af
UID PID PPID C STIME TTY TIME CMD root 3210 3166 0 15:51 pts/1 00:00:00 ps –af
这就是说,终端进程的pid是3166,我们在当前终端上执行了命令\-af\因此该终端进程启动了子进程并使其执行该命令,这个子进程的pid就是3210。 [root@localhost root]# ps –u os001
2、显示系统中的进程状态。试观察记录屏幕上的显示结果。 [root@localhost program]# top
21:00:23 up 2:26, 2 users, load average: 0.00, 0.01, 0.02 60 processes: 57 sleeping, 2 running, 1 zombie, 0 stopped
CPU states: 3.0% user 0.2% system 0.0% nice 0.0% iowait 96.8% idle Mem:117912k av,116168k used,1744k free,0k shrd,
7000k buff 73180k actv,396k in_d,1300k in_c
Swap: 265032k av, 73748k used, 191284k free 43000k cached
PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME CPU COMMAND 2998 root 16 0 24072 7992 716 R 2.1 6.7 2:10 0 X
3115 root 15 0 6528 5268 2632 S 0.7 4.4 0:12 0 gnome-terminal 1 root 15 0 108 84 56 S 0.0 0.0 0:04 0 init 2 root 15 0 0 0 0 S W 0.0 0.0 0:00 0 keventd ..........................
3、显示linux系统中的进程树。试观察分析屏幕上的显示结果。
[root@localhost program]# pstree
4、编写程序,使用fork( )创建两个子进程。观察在程序运行过程中的进程状态变化,分析原因。
(1)参照参考程序编写,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符串,其中,每个进程显示其pid值,及其父进程的pid值。类似如下显示:父进程显示'\,子进程分别显示'\child1.pid:..., ppid:.... \和'\。试观察并分析屏幕上的显示结果。 [root@localhost program]# ./fp
In parent when no child:pid:3750,ppid:3726 In child1:pid:3752,ppid:3750 In parent when one child: In child2:pid:3755,ppid:3750 In parent when two children:
(2)也可用ltrace -f -i -S ./executable-file-name查看以上程序执行过程。
从进程并发执行来看,上面的三个进程没有同步措施,只要进程就绪就可能执行,因此各种执行顺序都有可能,所以三个进程的输出次序带有随机性。并且,每当一个进程执行了一段时间,其它就绪进程可能抢占处理机,因此,多个进程可能交错执行。不过,操作系统实现函数printf( )时,保证了进程每次调用该函数输出一个字符串时不会被中断。另外,Linux缺省的调度策略 是FIFO和时间片轮转。
8
《操作系统原理及应用》实验指导书
5、Windows下的进程观察
按下CTRL-ALT-DEL (或在任务栏上点击鼠标右键选择任务管理器) 打开windows任务管理器对话框 。
a.选择“进程”标签,列出你的计算机上正在运行的三个进程 b.进程的标识符(PID)是唯一的吗?为什么?
c.找到当前CPU占用率最高的进程,写下它的名字、CPU占用率、以及内存使用量。 e.你的系统上总的物理内存和系统缓冲是多大?
参考程序
#include main( ) {
int p1,p2;
printf(\”);
while((p1=fork())== -1); /*创建子进程1*/ if (p1==0) {
printf(\子进程1段 } else {
printf(\父进程段a while((p2=fork())== -1); /*创建子进程2*/ if(p2==0) {
printf(\子进程2段 } else {
printf(\
pid:%d,ppid:%d\\n\父进程段b } } }
思考题
(1)在终端运行的用户执行程序,其父进程是谁?系统是怎样创建进程的? (2)当首次执行新创建的进程时,其入口在哪里?
9
《操作系统原理及应用》实验指导书
实验三 进程观察实验(二):进程的控制
实验目的
1、了解进程创建后对进程控制的系统调用,可实现对进程的有效控制
2、掌握进程的睡眠、同步、撤消等进程控制方法
实验内容
1、通过相关命令,对进程的状态进行控制。
2、编写程序,使用fork( )创建一个子进程。使用相关的系统调用控制进程的状态。观察并分析多进程的执行次序及状态转换。
实验基础
一、进程的控制
进程因创建而存在,因执行完成或异常原因而终止.在进程的生命周期中,进程在内存中有三种基本状态:就绪,执行,阻塞.进程状态的转换是通过进程控制原语来实现的。
Linux操作系统提供了相应功能的系统调用及命令,来实现用户层的进程控制。
现在我们开始讲述一下进程的控制,主要介绍内核对fork、exec、wait、exit的处理过程。 在Linux系统中,用户创建一个进程的唯一方法就是使用系统调用fork。内核为完成系统调用fork要进行几步操作第一步,为新进程在进程表中分配一个表项。系统对一个用户可以同时运行的进程数是有限制的,对超级用户没有该限制,但也不能超过进程表的最大表项的数目。第二步,给子进程一个唯一的进程标识号(PID)。该进程标识号其实就是该表项在进程表中的索引号。第三步,复制一个父进程的进程表项的副本给子进程。内核初始化子进程的进程表项时,是从父进程处拷贝的。所以子进程拥有与父进程一样的uid、euid、gid、用于计算优先权的nice值、当前目录、当前根、用户文件描述符表等。第四步,把与父进程相连的文件表和索引节点表的引用数加1。这些文件自动地与该子进程相连。第五步,内核为子进程创建用户级上下文。第六步,生成进程的动态部分,内核复制父进程的上下文的第一层,即寄存器上下文和内核栈,内核再为子进程虚设一个上下文层,这是为了子进程能“恢复”它的上下文。这时,该调用会对父进程返回子进程的pid,对子进程返回0。
Linux系统的系统调用exit,是进程用来终止执行时调用的。进程发出该调用,内核就会释放该进程所占的资源,释放进程上下文所占的内存空间,保留进程表项,将进程表项中纪录进程状态的字段设为僵死状态。内核在进程收到不可捕捉的信号时,会从内核内部调用exit,使得进程退出。父进程通过wait得到其子进程的进程表项中纪录的计时数据,并释放进程表项。最后,内核使得进程1(init进程)接收终止执行的进程的所有子进程。如果有子进程僵死,就向init进程发出一个SIGCHLD的软中断信号.
一个进程通过调用wait来与它的子进程同步,如果发出调用的进程没有子进程则返回一个错误,如果找到一个僵死的子进程就取子进程的PID及退出时提供给父进程的参数。如果有子进程,但没有僵死的子进程,发出调用的进程就睡眠在一个可中断的级别上,直到收到一个子进程僵死(SIGCLD)的信号或其他信号。
进程控制的另一个主要内容就是对其他程序引用。该功能是通过系统调用exec来实现的,该调用将一个可执行的程序文件读入,代替发出调用的进程执行。内核读入程序文件的正文,清除原先进程的数据区,清除原先用户软中断信号处理函数的地址,当exec调用返回时,进程执行新的正文。
讲一下进程调度的概念。Linux系统是一个分时系统,内核给每个进程分一个时间片,该进程的时间片用完就会调度另一个进程执行。LINUX系统上的调度程序属于多级反馈循环调度。该调度方法是,给一个进程分一个时间片,抢先一个运行超过时间片的进程,并把进程反馈到若干优先级
10
《操作系统原理及应用》实验指导书
队列中的一个队列。进程在执行完之前,要经过这样多次反馈循环。
进程调度分成两个部分,一个是调度的时机,即什么时候调度;一个是调度的算法,即如何调度和调度哪个进程。
二、相关的命令
(1)睡眠指定时间 执行格式:# sleep x
X为指定睡眠的秒数。 (2)结束或终止进程 kill
执行格式:# kill [-9] PID (PID为利用ps命令所查出的process ID) 例: kill -9 456 终止process ID 为456的process (3)后台(background)执行process command的命令 执行格式:# command & (在命令后加上 &) 例: gcc file1 & 在后台编译file1.c 注意:按下^Z,暂停正在执行的process。键入”bg”,将所暂停的process置入background中继续执行。
例:# gcc file1 & ^Z stopped # bg
(4)查看正在background中执行的process 执行格式:# jobs
(5)结束或终止在background中的进程 kill 执行格式:# kill %n
例: kill %1 终止在background中的第一个job kill %2 终止在background中的第二个job
三、相关的系统调用
在LINUX中fork( )是一个非常有用的系统调用,但在LINUX中建立进程除了fork( )之外,也可用与fork( ) 配合使用的exec( )。
1、exec( )系列
系统调用exec( )系列,也可用于新程序的运行。fork( )只是将父进程的用户级上下文拷贝到新进程中,而exec( )系列可以将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上,以更改新进程的用户级上下文。exec( )系列中的系统调用都完成相同的功能,它们把一个新程序装入内存,来改变调用进程的执行代码,从而形成新进程。如果exec( )调用成功,调用进程将被覆盖,然后从新程序的入口开始执行,这样就产生了一个新进程,新进程的进程标识符id 与调用进程相同。
exec( )没有建立一个与调用进程并发的子进程,而是用新进程取代了原来进程。所以exec( )调用成功后,没有任何数据返回,这与fork( )不同。exec( )系列系统调用在LINUX系统库unistd.h中,共有execl、execlp、execle、execv、execvp五个,其基本功能相同,只是以不同的方式来给出参数。
一种是直接给出参数的指针,如: int execl(path,arg0[,arg1,...argn],0); char *path,*arg0,*arg1,...,*argn;
另一种是给出指向参数表的指针,如: int execv(path,argv); char *path,*argv[ ];
另外,在linux中,出于安全的考虑,限制了exec()可以执行的新程序的位置为系统指定的搜索路径。
例如:
execl(“/bin/ls”,”ls”,NULL);
execl(“/usr/bin/gcc”,”-v”,NULL);
execl(“./test”,NULL); //当前目录下的可执行程序 2、exec( )和fork( )联合使用
11