【Linux】实验报告4 进程管理

实验四 进程管理

实验目录

实验目的

通过实际上机调试和运行程序了解 Linux 系统中进程的基本编程;了解父进程和子进程的概念

实验原理

1.1 Linux中的进程
  • 在Linux系统中,进程 被认为是 具有一定功能的程序在一个数据集上的一次运行活动,是处于活动状态的计算机程序,操作系统是通过进程去完成一个个任务,进程是管理事务的基本单元

  • Linux是一个 多进程 的系统,进程之间具有 互不干扰 的特点。其中,每个进程都运行在各自独立的虚拟地址空间。因此,即使一个进程发生了异常,它也不会影响到系统的其他进程。创建一个进程时,系统会分配0—4G 虚拟地址空间,其中0—3G 是用户空间, 3—4G是内核空间,多个进程共用同一份内核,各自进程的用户空间是独立的

1.2 进程的基本状态
  • 就绪态 :程序满足执行的条件 ,等待CPU分配时间片
  • 执行态: CPU分配时间片给当前进程 ,进程正在占用CPU执行发任务的过程
  • 等待态 :程序在执行过程中,不满足条件,而进行睡眠状态
image-20220519160553669
1.3 进程的实体结构

1. 进程控制块(PCB)

  • PCB是操作系统为了管理进程设置的一个专门的 结构体 task_struct

  • 操作系统用它来记录进程的外部特征,描述进程的运动变化过程,系统也可以利用PCB来控制和管理进程

image-20220528154700586

2. 程序段

  • 位于内存中,存放该程序的代码

3. 数据段

  • 位于内存中,存放该程序运行过程中存放的各种数据(如变量等)
1.4 进程存储空间

img

内存空间代码验证

#include <stdio.h>
#include <stdlib.h>

int global_init_val = 100;
int global_noninit_val;

int main(int argc,char *argv[],char * envp[]){
        static int local_static_val = 1000;
        char *local_val;
        local_val = malloc(10);
        printf("address of text:%p\n",main);
        printf("address of data:%p %p\n",&global_init_val,&local_static_val);
        printf("address of bss:%p\n",&global_noninit_val);
        printf("address of heap:%p\n",local_val);
        printf("address of stack:%p\n",&local_val);
        free(local_val);

        printf("&environ = %p, environ = %p\n",&envp,envp);
        printf("&argv = %p, argv = %p\n",&argv,argv);
        return 0;
}

image-20220528164021913

1.5 进程号PID&PPID

每个进程都由一个进程号 PID 来标识,其类型为 pid_t(无符号整型),进程号的范围:0~32767。进程号总是 唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用。

进程除了 PID 外,还有 PPID (父进程号),任何进程( 除 init 进程)都是由另一个进程创建。所有进程的祖先进程是同一个进程,它叫做init 进程,所以,在 Linux 下面所有的进程都由 init 进程直接或者间接创建。init 进程的PID 为 1, 是内核的第一个启动用户的进程。

image-20220519162437009

缩写中文名称描述获取方式
PID进程号标识进程的一个非负整型数。pid_t getpid(void);
PPID父进程号若A进程创建了 B 进程,A 的进程号就是 B 进程的父进程号。pid_t getppid(void);
1.6 进程相关函数
所需头文件
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<wait.h>
getpid( )
pid_t getpid()  #获取当前进程的PID号
getppid( )
pid_t getppid() #获取当前进程的PID号
fork( )
pid_t pid=fork()  #创建一个新的进程
原型pid_t fork(void);
函数功能创建一个子进程
返回值fork调用一次返回两次,父进程中返回子进程的ID号
  • 父、子进程是 完全一样的(代码、数据),子进程从fork内部开始执行、fork返回子进程的pid后,接着执行下一条语句

  • fork( )调用的特点是 调用一次,返回两次,它可能有三种不同的返回值:

    1. 在父进程中,fork返回新创建子进程PID
    2. 在子进程中,fork返回0
    3. 如果出现错误,fork返回一个负值

fork实例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main(void){
        pid_t pid=fork();       //fork时,子进程诞生,子进程从fork语句后开始执行

        if(pid==0){             //子进程
                printf("PID=%d, PPID=%d\n",getpid(),getppid());
                printf("我是子进程\n");
        }else if(pid>0){
                printf("PID=%d, PPID=%d\n",getpid(),getppid());
                printf("我是父进程\n");
        }
        return 0;
}

image-20220602113829971

fork函数视频讲解

fork函数视频讲解2

wait( )
pid_t wait(int *status)
原型pid_t wait(int *status)
函数功能调用wait后父进程将被阻塞,wait会分析当前进程的某个子进程是否已经退出。如果它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它销毁后返回;如果没有找到这样一个子进程,wait会一直阻塞,直到有一个出现为止。
返回值返回被收集的子进程的PID,若调用进程没有子进程,此时返回-1,同时errno被置为ECHILD。

当父进程没有等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时的子进程就是 僵尸进程,进而造成内存泄露。进程一旦变成僵尸状态,强制杀死进程的”kill -9”也无能为力,所以我们需要知道子进程的任务完成的如何,是否正常退出。

因此,我们需要让父进程等待自己的子进程,或者 父进程回收自己的子进程资源 包括僵尸进程。我们通过wait( )函数来完成这一功能

参数status 用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL

注意点

  • wait( )要与fork( )配套出现,如果在使用fork()之前调用wait( ),wait( )的返回值为-1,正常情况下wait( )的返回值为子进程的PID

  • 如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID=1)继承,当子进程终止时,init进程捕获这个状态

wait函数视频讲解

exec( )

使用fork( )函数创建子进程后,子进程通常会调用 exec函数 来执行另外一个程序

当进程调用exec函数时,该进程由新程序完全替换,即用一个可执行程序 代替当前进程的执行映像,而新程序则 从其main函数开始执行。因为调用exec并不创建新进程,所以 前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。

exec函数视频讲解

实验作业

1. fork.c实践

源代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
        pid_t pid;
        printf("Process Creation Study\n");
        pid = fork();
        switch(pid) {
        case 0:
                printf("Child process is running,CurPid is %d,ParentPid is %d\n", getpid(), getppid());
                break;
        case -1:
                perror("Process creation failed\n");
                break;
        default:
                printf("Parent process is running,CurPid is %d,ChildPid is %d\n", getpid(), pid);
                break;
        }
        return 0;
}
问题描述

请回答,程序的运行结果是什么?并且分析程序输出这个结果的原因。

程序运行结果

image-20220602112144317

结果分析
  • 运行父进程时,pid=295319,即为子进程的PID;同时getpid( )返回了该父进程的PID值295318
  • 运行子进程时,pid=0,由getpid( )获取子进程的PID值295319;同时getppid( )返回了其PPID值295318

2. fork2.c实践

源代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
	pid_t pid;
	char * msg;
	int k;
	printf("Process Creation Study\n");
	pid = fork();
	switch(pid){
	case 0:
		msg = "Child process is running";
		k = 3;
		break;
	case -1:
		perror("Process creation failed\n");
		break;
	default:
		msg = "Parent process is running";
		k=5;
		break;
	}
	while(k > 0)
	{
		puts(msg);
		sleep(1);
		k--;
	}
	exit(0);
}
问题描述

请回答,程序的运行结果是什么?并且分析程序输出这个结果的原因。

程序运行结果

image-20220602111302161

结果分析
  • 该代码验证了fork( )函数 调用子进程、父进程的顺序。一般来说,fork( )之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的算法

  • 从代码结果可见,fork( )函数对两者的调用是 交替进行 的,否则得到的结果应该是连续输出5句”Parent process is running“以及连续输出3句"Child process is running",而不是两者交替输出。

3. processimage.c实践

源代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[], char **environ){
		int i;
		printf("I am a process image!\n");
		printf("My pid = %d, parentpid = %d\n",getpid(), getppid());
		printf("uid:%d, gid%d\n"getuid(), getgid());
		for(i=0; i< argc; i++)
				printf("argv[%d]:%s\n",i ,argv[i]);
}
问题描述

回答:程序执行的结果是什么?请分析改程序的执行过程。

程序运行结果

image-20220602110356986

结果分析
  • argc 表示传入main函数的参数个数

  • argv[] 表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,所以确切的说需要我们输入的main函数的参数个数应该是argc-1个

4. exec.c实践

源代码
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <unistd.h>
#include <stdlib.h>

int main(int argc,char * argv[],char ** environ) {
		pid_t pid;
    int stat_val;
		printf("Exec example!\n"); 
  	pid = fork(); 
    switch(pid) {
				case -1:
						perror("Process Creation failed\n"); 
        		exit(1);
      	case 0:
						printf("Child process is running\n");
        		printf("My pid = %d ,parentpid = %d\n",getpid(),getppid());
						printf("uid = %d,gid =%d\n",getuid(),getgid());
						execve("processimage.out",argv, environ);
        		printf("process never go to here!\n");
        		exit(0); 
      default:
					  printf("Parent process is running\n"); 
        	  break;
			}
			wait(&stat_val); 
  		exit(0);
}
问题描述

回答:程序执行的结果是什么?请分析改程序的执行过程。

程序运行结果

image-20220602110525829

结果分析
  • 在本次程序代码运行过程中,fork( )函数首先调用了父进程,即先输出了“Parent process is running"

  • 之后fork( )函数调用了子进程,正常执行到line17。在line18处,由于execve命令,系统调用processimage.out的内容执行并且覆盖原代码,故line19不会被执行,即”process never go to here!“不会输出在屏幕上

5. wait.c实践

源代码
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <unistd.h>
#include <stdlib.h>

int main() {
		pid_t pid;
    char *msg; 
  	int k; 
  	int exit_code;

		printf("Study how to get exit code\n"); 
  	pid = fork(); 
   	switch(pid)	{
				case 0:
						msg = "Child process is running";
						k = 5;
						exit_code = 37;
        		break; 
     		case -1:
						perror("Process creation failed\n"); 
        		exit(1); 
      	default:
						exit_code = 0;
        		break;
		} 
  	
    /* 父子进程都会执行以下这段代码子进程中pid值为0,父进程pid值为子进程的ID */
		if(pid != 0){ // 父进程等待子进程结束 
    			int stat_val; 
      		pid_t child_pid;
      
					child_pid = wait(&stat_val);
      
					printf("Child procee has exited, pid = %d\n",child_pid); 
      		if(WIFEXITED(stat_val)) 
        			printf("Child exited with code %d\n",WEXITSTATUS(stat_val)); 
      		else 								
      				printf("Child exited abnormally\n");
    	}else{		    // 子进程暂停5秒,在这个过程中可以运行命令ps aux查看父进程状态
						while(k-->0) { 
            		puts(msg); 
            		sleep(1); 
            }
			} 
  		exit(exit_code);
}
问题描述

回答:程序运行的结果是什么?请根据结果结合程序代码分析父进程等待子进程的。

程序运行结果

image-20220602110259543

结果分析
  • 由于line34 wait( )的存在,使得父进程暂时处于阻塞状态,需要等待被fork( )出来的子进程执行完毕,才会继续执行父进程的代码。因此,该源代码首先输出了子进程的内容,即连续输出6句“Child process is running”
  • 等待子进程结束后,wait( )回收子进程资源,并且将status赋值为子进程退出的状态值,最后将父进程的输出内容打印在屏幕上
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值