16281035-操作系统实验2

操作系统第二次实验:进程控制

16281035
我的github网址,里面有源文件,请查看

实验目的

  • 加深对进程概念的理解,明确进程和程序的区别。
  • 掌握Linux系统中的进程创建,管理和删除等操作。
  • 熟悉使用Linux下的命令和工具,如man, find, grep, whereis, ps, pgrep, kill, ptree, top, vim, gcc,gdb, 管道|等。

基础知识

  • 进程的创建

Linux中,载入内存并执行程序映像的操作与创建一个新进程的操作是分离的。将程序映像载入内存,并开始运行它,这个过程称为运行一个新的程序,相应的系统调用称为exec系统调用。而创建一个新的进程的系统调用是fork系统调用。

  • exec系统调用

#include

int execl (const char *path, const char *arg,…);

execl()将path所指路径的映像载入内存,arg是它的第一个参数。参数可变长。参数列表必须以NULL结尾。

通常execl()不会返回。成功的调用会以跳到新的程序入口点作为结束。发生错误时,execl()返回-1,并设置errno值。

例 编辑/home/kidd/hooks.txt:

int ret;

ret = execl (”/bin/vi”, ”vi”,”/home/kidd/hooks.txt”, NULL);

if (ret == -1)

perror (”execl”);

  • fork****系统调用

#include

#include

pid_t fork (void);

成功调用fork()会创建一个新的进程,它与调用fork()的进程大致相同。发生错误时,fork()返回-1,并设置errno值。

例:

pid_t pid;

pid = fork ();

if (pid > 0)

printf (”I am the parent of pid=%d!\n”, pid);

else if (!pid)

printf (”I am the baby!\n”);

else if (pid == -1)

perror (”fork”);

  • 终止进程

exit()系统调用:

#include

void exit (int status);

  • 进程挂起

pause() 系统调用:

int pause( void );

函数pause会把进程挂起,直到接收到信号。在信号接收后,进程会从pause函数中退出,继续运行。

  • wait(等待子进程中断或结束)

#include

#include

pid_t wait (int * status);

wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。

如果在调用 wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。

子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一起返回。

如果不在意结束状态值,则参数status可以设成 NULL。

VIM常用命令速查

16281035-操作系统实验2_第1张图片

实验题目

1. 打开一个vi进程。通过ps命令以及选择合适的参数,只显示名字为vi的进程。寻找vi进程的父进程,直到init进程为止。记录过程中所有进程的ID和父进程ID。将得到的进程树和由pstree命令的得到的进程树进行比较。

  1. 首先我先自己创建一个名为VI的vim文件,之后用vim打开
    1552704979565

  2. 打开界面如下:
    我在其中启用了编辑命令,编辑了以下内容:
    16281035-操作系统实验2_第2张图片

  3. 保存
    16281035-操作系统实验2_第3张图片

  4. 查看vim进程号,5065

    ![1552705140843](https://img-blog.csdnimg.cn/20190316114007384.png
    在这里插入图片描述

  5. 寻找vim进程的父进程,直到init进程为止。记录过程中所有进程的ID和父进程ID
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    init进程的pid是1.

    init进程是其他所有进程的父进程.

    列出父进程号是1的进程,就是列出除了init外的所有进程.

    所以寻找vim的父进程如下:

    ​ 5065 -> 4626 -> 4619 -> 908 -> 1

  6. 将得到的进程树和由pstree命令的得到的进程树进行比较。

16281035-操作系统实验2_第4张图片

16281035-操作系统实验2_第5张图片
两个相比较,是一样的结果: 5065 -> 4626 -> 4619 -> 908 -> 1

2、编写程序,首先使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。然后在另外一个终端中,通过ps –Al命令、ps aux或者top等命令,查看vi进程及其父进程的运行状态,理解每个参数所表达的意义。选择合适的命令参数,对所有进程按照CPU占用率排序。

  1. 程序代码 fork.c

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[]){
    pid_t pid = fork();
    
    //在子进程
    if(pid == 0)
    {
    int num = execl("/usr/bin/vim","vim","NULL");
    if(num == -1)
    perror("execl");
    }
    
    else if(pid>0)
    {
    //在父进程
    while(1){}
    }
    
    else
    {
    printf("fork调用失败");
    }
    return (0);
    }
    
  2. 运行结果:打开了vim

    在这里插入图片描述
    在这里插入图片描述
    16281035-操作系统实验2_第6张图片

  3. 查看vi进程及父进程的运行状态以及各个参数的意思:

    16281035-操作系统实验2_第7张图片
    16281035-操作系统实验2_第8张图片

  4. 选择合适的命令参数,对所有进程按照CPU占用率排序

在这里插入图片描述
在这里插入图片描述

3、使用fork系统调用,创建如下进程树,并使每个进程输出自己的ID和父进程的ID。观察进程的执行顺序和运行状态的变化。

16281035-操作系统实验2_第9张图片

  1. 首先对fork()函数调用进行简单的测试
    16281035-操作系统实验2_第10张图片

​ 运行结果
16281035-操作系统实验2_第11张图片
输出两个hello

所以:函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。需要注意的一点:就是调用fork函数之后,一定是两个进程同时执行的代码段是fork函数之后的代码,而之前的代码以及由父进程执行完毕。

fork()返回值意义如下:

=0:在子进程中

0:在父进程中

<0:创建失败

  1. 源代码 fork3.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include //这个头文件不能少,否则pid_t没有定义 
    
    int main(int argc, char *argv[])
    {
    pid_t pidA,pidB,pidC,pidD;
    printf("这是父进程,P1,PID是%d\n",getpid());
    
    pidA = fork(); //创建新进程
    
    if(pidA<0)
    {
    printf("新进程创建失败\n");
    return 0;
    }
    
    else if(pidA == 0)
    {
    printf("这是子进程,P3,PID是%d\n",getpid());
    return 0;
    }
    
    else
    {
    pidB = fork();
    if(pidB<0)
    {
    printf("新进程创建失败\n");
    return 0;
    }
    
    else if(pidB == 0)
    {
    printf("我是子进程,P2,PID是%d\n",getpid());
    
    pidC = fork();
    if(pidC == 0)
    {
    printf("这是子进程,P4,PID是%d\n",getpid());
    return 0;//防止产生孙进程
    }
    
    pidD = fork();
    if(pidD == 0)
    {
    printf("这是子进程,P5,PID是%d\n",getpid());
    return 0;//防止产生孙进程
    }
    }
    }
    return 0;
    }
    
    
  2. 运行结果
    16281035-操作系统实验2_第12张图片

  3. 代码改进:我发现代码如上述,不论是父进程还是子进程都仍旧在执行,不会停止。

    所以我在原来的基础上加入了如下的代码:

     int *status;
                 waitpid(pidC,status, 0);
                 waitpid(pidD,status, 0);
                 return 0;
            }
        }
        int *status;
        waitpid(pidB,status, 0);
        waitpid(pidA,status, 0);
        return 0;
    
    

    得到的运行结果自动停止
    16281035-操作系统实验2_第13张图片

4、修改上述进程树中的进程,使得所有进程都循环输出自己的ID和父进程的ID。然后终止p2进程(分别采用kill -9 、自己正常退出exit()、段错误退出),观察p1、p3、p4、p5进程的运行状态和其他相关参数有何改变。

  1. kill -9

    16281035-操作系统实验2_第14张图片

    在杀掉进程P2,进程号为5228之后,P4和P5的PPID不再是5228,而变成了895.

    我在进程树中查询,发现systemd(895), 也就是相当于最原始的一个根进程。那么我就有很多的问题:

    (1)为什么这些失去父进程的“孤儿”子进程的PPID不是systemd(1)?

    (2)systemd(1)和systemd(895)之间的关联是什么?

  2. 自己正常退出

    源代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include //这个头文件不能少,否则pid_t没有定义 
    
    int main(int argc, char *argv[])
    {
    pid_t pidA,pidB,pidC,pidD;
    int i;
    printf("这是父进程,P1,PID是%d\n",getpid());
    
    pidA = fork(); //创建新进程
    
    if(pidA<0)
    {
    printf("新进程创建失败\n");
    exit(0);
    }
    
    else if(pidA == 0)
    {
    i=100;
    while(i-->0)
    {
    printf("这是子进程,P3,PID是%d 它的父进程,P1,PID是%d\n",getpid(),getppid());
    }
    return 0;
    }
    
    else
    {
    pidB = fork();
    if(pidB<0)
    {
    printf("新进程创建失败\n");
    return 0;
    }
    
    else if(pidB == 0)
    {
    i=100;
    pidC = fork();
    if(pidC == 0)
    {
    i=100;
    while(i-->0){
    printf("这是子进程,P4,PID是%d 它的父进程,P2,PID是%d\n",getpid(),getppid());}
    return 0;//防止产生孙进程
    }
    
    pidD = fork();
    if(pidD == 0)
    {
    i=100;
    while(i-->0){
    printf("这是子进程,P5,PID是%d 它的父进程,P2,PID是%d\n",getpid(),getppid());
    }
    return 0;//防止产生孙进程
    }
    i=100;
    while(i-->0)
    {
    printf("这是子进程,P2,PID是%d 它的父进程,P1,PID是%d\n",getpid(),getppid());
    if(i==50) {
    	exit(0);
    }
    }
    
    int *status;
    waitpid(pidC,status, 0);
    waitpid(pidD,status, 0);
    return 0;
    }
    }
    int *status;
    waitpid(pidB,status, 0);
    waitpid(pidA,status, 0);
    return 0;
    
    }
    

    运行结果:

和第1问结果一样,只不过它运行过快,未自动结束前的部分没来得及截图。
16281035-操作系统实验2_第15张图片
3. 段错误退出

源代码


#include 
#include 
#include 
#include 
#include 
#include //这个头文件不能少,否则pid_t没有定义 

int main(int argc, char *argv[])
{
pid_t pidA,pidB,pidC,pidD;
int i;
printf("这是父进程,P1,PID是%d\n",getpid());

pidA = fork(); //创建新进程

if(pidA<0)
{
printf("新进程创建失败\n");
exit(0);
}

else if(pidA == 0)
{
i=100;
while(i-->0)
{
printf("这是子进程,P3,PID是%d 它的父进程,P1,PID是%d\n",getpid(),getppid());
}
return 0;
}

else
{
pidB = fork();
if(pidB<0)
{
printf("新进程创建失败\n");
return 0;
}

else if(pidB == 0)
{
i=100;
pidC = fork();
if(pidC == 0)
{
i=100;
while(i-->0){
printf("这是子进程,P4,PID是%d 它的父进程,P2,PID是%d\n",getpid(),getppid());}
return 0;//防止产生孙进程
}

pidD = fork();
if(pidD == 0)
{
i=100;
while(i-->0){
printf("这是子进程,P5,PID是%d 它的父进程,P2,PID是%d\n",getpid(),getppid());
}
return 0;//防止产生孙进程
}
i=100;
while(i-->0)
{
printf("这是子进程,P2,PID是%d 它的父进程,P1,PID是%d\n",getpid(),getppid());
if(i==50) {
int *ptr = NULL;
*ptr = 0;
}
}

int *status;
waitpid(pidC,status, 0);
waitpid(pidD,status, 0);
return 0;
}
}
int *status;
waitpid(pidB,status, 0);
waitpid(pidA,status, 0);
return 0;

}

运行结果:
在这里插入图片描述
在这里插入图片描述
段错误退出从以上结果来看不会影响P2?它仍旧是P4和P5的父进程。

你可能感兴趣的:(16281035-操作系统实验2)