目录
一、实验内容:
a) 观察进程调度
b) 观察进程调度中的全局变量改变
c) 在子进程中调用system函数、exec函数
二、实验成果:
(1)多次运行程序后发现,输出顺序并不是一成不变的,这是由于系统采用并行执行的原因,但同一代码块的输出顺序不会发生改变;
(2)去除`wait`之后,我们发现结果变化不大,根据分析,这是因为父进程结束之后,子进程仍然继续执行原因;
(3)添加一个全局变量之后,在父子进程中对改变量进行不同的操作,但都会按照(1)中的结论进行输出,且该变量的值不一样,这是因为父子进程各自有一个独立的地址空间;
(4)在`return`前对全局变量进行操作,我们可以发现父子进程各有一个输出结果且输出的结果并不相同,这是因为调用了`fork`函数,`return`前的那一段代码段父子进程都会各自执行一遍;
(5)当我们调用system函数时,父进程创建新的子进程运行目标程序,并等待这个子进程运行结束;而调用exec函数时,原先的子进程已经被 `/bin/bash` 取而代之;
三、实验过程:
3.1 基本实现
3.1.1 基本代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid,pid1;
//fork a child process
pid=fork();
if(pid<0)//error occurred
{
fprintf(stderr,"Fork Failed");
return 1;
}
else if(pid==0)//child process
{
pid1=getpid();
printf("child:pid=%d",pid);
printf("child:pid1=%d",pid1);
}
else//parent process
{
pid1=getpid();
printf("parent:pid=%d",pid);
printf("parent:pid1=%d",pid1);
wait(NULL);
}
return 0;
}
根据题目要求,基本代码如上,将该程序在Linux操作系统下实现。
首先,对该段程序进行分析,在调用fork函数创建一个新进程之后,在父进程中返回子进程pid
,在子进程中返回0。因此,else if
代码块为子进程执行的代码,else
代码块为父进程执行的代码。预期输出为:
child: pid=0
chile: pid1=子进程PID
parent: pid=子进程PID
parent: pid1=父进程PID
然而,在系统调用两个进程时可能并行执行,并按照任意顺序持有stdout
的锁,从而导致实际输出顺序会不断发生变化,但同一代码块的输出顺序保持不变。
在华为云服务器中运行,以验证结果:
多次运行结果之后发现实际情况与预期相同由于操作系统实际调度情况不同,输出顺序可能以不同的方式交错,但总是保持每个进程自身的输出顺序不变。
3.1.2 去除wait
值得一提的是,在去除wait之后,输出的结果并没有发生很大变化:
对此,我预期其原因是:父进程结束之后,子进程仍然继续工作。为了进一步了解此时父子进程的关系,在父进程和子进程中插入死循环,通过 kill 命令观察现象。部分代码实现如下:
if (pid < 0) {
fprintf(stderr, "Fork failed");
return 1;
} else if (pid == 0) {
pid1 = getpid();
printf("child: pid = %d\n", pid);
printf("child: pid1 = %d\n", pid1);
while (1);
} else {
pid1 = getpid();
printf("parent: pid = %d\n", pid);
printf("parent: pid1 = %d\n", pid1);
while (1);
}
1、运行程序发现,程序并未结束,父子进程均陷入死循环,CPU的占用率也居高不下:
2、对此,我们可以使用pstree命令来查看进程树:
3、接着,使用 kill 命令杀死父进程kill 276581
,并再次观察进程树变化。可以发现子进程存活,成为孤儿进程,并被 init 进程收留,成为 init 的直接子进程。
由此可见,杀死父进程之后,子进程并不一定就退出;如果父进程先于子进程退出,那么子进程成为孤儿进程并被init进程收留。
3.2.1 父子进程进行不同的操作
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
static int a=0;
int main()
{
pid_t pid,pid1;
//fork a child process
pid=fork();
if(pid<0)//error occurred
{
fprintf(stderr,"Fork Failed");
return 1;
}
else if(pid==0)//child process
{
pid1=getpid();
printf("child:pid=%d\n",pid);
printf("child:pid1=%d\n",pid1);
printf("child:abefore=%d\n",a);
a=2;
printf("child:aafter=%d\n",a);
}
else//parent process
{
pid1=getpid();
printf("parent:pid=%d\n",pid);
printf("parent:pid1=%d\n",pid1);
printf("parent:abefore=%d\n",a);
a=1;
printf("parent:aafter=%d\n",a);
}
return 0;
}
由于父子进程各有其运行空间,因此父子进程中的不同操作互不影响,预期输出结果:
parent:abefore=0
parent:aafter=1;
child:abefore=0;
child:aafter=2;
在系统中运行,可验证结论:
3.2.2 return前对全局变量进行操作
我们在return指令之前对全局变量进行操作,由于父子进程各有其独立的运行空间,因此对于这一段代码都会独立的运行一遍。我们预期输出结果如下:
parent:abefore=0;
parent:aafter=1;
pid:父进程PID,before:1;
pid:父进程PID,after:3;
child:abefore=0;
child:aafter=2;
pid:子进程PID,before:2;
pid:子进程PID,after:3;
在3.2.1的基础上增添部分代码如下:
printf("pid1:%d,before:%d\n",pid1,a);
a=3;
printf("pid1:%d,after:%d\n",pid1,a);
由此确认结论无误。
3.3 调用system函数、exec函数
3.3.1 调用system函数
system()
会调用fork()
产生子进程,由子进程来调用/bin/sh-c string
来执行参数string
字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()
期间SIGCHLD
信号会被暂时搁置,SIGINT
和SIGQUIT
信号则会被忽略。
代码如下:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid,pid1;
//fork a child process
pid=fork();
if(pid<0)//error occurred
{
fprintf(stderr,"Fork Failed");
return 1;
}
else if(pid==0)//child process
{
pid1=getpid();
printf("child:pid=%d",pid);
printf("child:pid1=%d",pid1);
}
else//parent process
{
pid1=getpid();
printf("parent:pid=%d",pid);
printf("parent:pid1=%d",pid1);
system("sleep 10");
printf("over");
wait(NULL);
}
return 0;
}
子进程正常执行,父进程等待10s后正常退出。
3.3.2 调用exec函数
我们用fork函数创建新进程后,经常会在新进程中。 当进程调用exec函数时,该进程被完全替换为新程序。 因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
我们在子进程执行的代码块中加入exec族函数:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid,pid1;
//fork a child process
pid=fork();
if(pid<0)//error occurred
{
fprintf(stderr,"Fork Failed");
return 1;
}
else if(pid==0)//child process
{
pid1=getpid();
printf("child:pid=%d\n",pid);
printf("child:pid1=%d\n",pid1);
execl("/bin/bash","bash","-c","echo Before;sleep 5;echo After");
while(1);
}
else//parent process
{
pid1=getpid();
printf("parent:pid=%d\n",pid);
printf("parent:pid1=%d\n",pid1);
wait(NULL);
printf("over");
}
return 0;
}
我们可以看到子进程execl函数之后的死循环代码并未执行,因此说明此时原先的子进程已经被 `/bin/bash` 取而代之。