3. 所涉及的系统调用
fork( ) 创建一个新进程。 系统调用格式:
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( )返回值的不同,执行了不同的分支语句。
例:
….. PC pid=fork( );
if (! pid)
printf(\
else if (pid>0)
printf(\
else
printf(\
…… fork( )调用前 fork( )调用后 ….. pid=fork( ); PC if (! pid) printf(\else if (pid>0) printf(\ else printf(\……
PC ….. pid=fork( ); if (! pid) printf(\else if (pid>0) printf(\ else printf(\…… 19
5. 参考程序 (1)
#include
int p1,p2;
while((p1=fork( ))= = -1); /*创建子进程p1*/ if (p1= =0) putchar('b'); else
{
while((p2=fork( ))= = -1); /*创建子进程p2*/ if(p2= =0) putchar('c'); else putchar('a'); }
} (2)
#include
int p1,p2,i;
while((p1=fork( ))= = -1); /*创建子进程p1*/ if (p1= =0)
for(i=0;i<10;i++)
printf(\ %d\\n\
else
{
while((p2=fork( ))= = -1); /*创建子进程p2*/
if(p2= =0)
for(i=0;i<10;i++)
printf(\ %d\\n\
else
for(i=0;i<10;i++)
printf(\ %d\\n\
}
}
运行结果
(1) bca,bac, abc ,……都有可能。 (2) parent… son… daughter.. daughter.. 或 parent… son… parent… daughter…等 原因分析
除strace 外,也可用ltrace -f -i -S ./executable-file-name查看以上程序执行过程。 (1) 从进程并发执行来看,各种情况都有可能。上面的三个进程没有同步措施,所以父进程与子进程的输出内容会叠加在一起。输出次序带有随机性。
(2) 由于函数printf( )在输出字符串时不会被中断,因此,字符串内部字符顺序输出不变。但由于进程并发执行的调度顺序和父子进程抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。
补充:进程树
在LINUX系统中,只有0进程是在系统引导时被创建的,在系统初启时由0进程创建
20
1进程,以后0进程变成对换进程,1进程成为系统中的始祖进程。LINUX利用fork( )为每个终端创建一个子进程为用户服务,如等待用户登录、执行SHELL命令解释程序等,每个终端进程又可利用fork( )来创建其子进程,从而形成一棵进程树。可以说,系统中除0进程外的所有进程都是用fork( )创建的。
6.思考题
(1) 系统是怎样创建进程的?
(2) 当首次调用新创建进程时,其入口在哪里?
21
实验4 观察Linux进程的同步与互斥
一、实验目的
1.掌握进程另外的创建方法
2.熟悉进程的睡眠、同步、撤消等进程控制方法 3.进一步认识并发执行的实质
4.分析进程竞争资源的现象,学习解决进程互斥的方法 二、实验内容
1.用fork( )创建一个进程,再调用exec( )用新的程序替换该子进程的内容
2.利用wait( )来控制进程执行顺序
3.修改实验上述,用lockf( )来给每一个进程加锁,以实现进程之间的互斥 4. 观察并分析出现的现象
三、实验指导
1.所涉及的系统调用
在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[ ];
具体使用可参考有关书。 (2) exec( )和fork( )联合使用
系统调用exec和fork( )联合使用能为程序开发提供有力支持。用fork( )建立子进程,然后在子进程中使用exec( ),这样就实现了父进程与一个与它完全不同子进程的并发执行。
一般,wait、exec联合使用的模型为: int status; ............
if (fork( )= =0) {
...........; execl(...); ...........; }
22
wait(&status); (3) wait( )
等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。
系统调用格式: int wait(status) int *status;
其中,status是用户空间的地址。它的低8位反应子进程状态,为0表示子进程正常结束,非0则表示出现了各种各样的问题;高8位则带回了exit( )的返回值。exit( )返回值由系统给出。
核心对wait( )作以下处理:
首先查找调用进程是否有子进程,若无,则返回出错码;
若找到一处于―僵死状态‖的子进程,则将子进程的执行时间加到父进程的执行时间上,并释放子进程的进程表项;
若未找到处于―僵死状态‖的子进程,则调用进程便在可被中断的优先级上睡眠,等待其子进程发来软中断信号时被唤醒。
(4) exit( )
终止进程的执行。 系统调用格式:
void exit(status) int status;
其中,status是返回给父进程的一个整数,以备查考。 为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。
如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。核心须为exit( )完成以下操作:
① 关闭软中断 ② 回收资源 ③ 写记帐信息 ④ 置进程为―僵死状态‖ (5) lockf(files, function, size)
用作锁定文件的某些段或者整个文件。 本函数的头文件为
#include \
参数定义:
int lockf(files,function,size) int files,function; long size;
其中:files是文件描述符;function是锁定和解锁:1表示锁定,0表示解锁。size是锁定或解锁的字节数,为0,表示从文件的当前位置到文件尾。
2.参考程序1 #include
int pid;
pid=fork( ); /*创建子进程*/
switch(pid)
{
case -1: /*创建失败*/ printf(\
23