实验四 进程管理
实验目录
文章目录
实验目的
通过实际上机调试和运行程序了解 Linux 系统中进程的基本编程;了解父进程和子进程的概念
实验原理
1.1 Linux中的进程
-
在Linux系统中,进程 被认为是 具有一定功能的程序在一个数据集上的一次运行活动,是处于活动状态的计算机程序,操作系统是通过进程去完成一个个任务,进程是管理事务的基本单元
-
Linux是一个 多进程 的系统,进程之间具有 互不干扰 的特点。其中,每个进程都运行在各自独立的虚拟地址空间。因此,即使一个进程发生了异常,它也不会影响到系统的其他进程。创建一个进程时,系统会分配0—4G 虚拟地址空间,其中0—3G 是用户空间, 3—4G是内核空间,多个进程共用同一份内核,各自进程的用户空间是独立的
1.2 进程的基本状态
- 就绪态 :程序满足执行的条件 ,等待CPU分配时间片
- 执行态: CPU分配时间片给当前进程 ,进程正在占用CPU执行发任务的过程
- 等待态 :程序在执行过程中,不满足条件,而进行睡眠状态
1.3 进程的实体结构
1. 进程控制块(PCB)
-
PCB是操作系统为了管理进程设置的一个专门的 结构体
task_struct
-
操作系统用它来记录进程的外部特征,描述进程的运动变化过程,系统也可以利用PCB来控制和管理进程
2. 程序段
- 位于内存中,存放该程序的代码
3. 数据段
- 位于内存中,存放该程序运行过程中存放的各种数据(如变量等)
1.4 进程存储空间
内存空间代码验证
#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;
}
1.5 进程号PID&PPID
每个进程都由一个进程号 PID
来标识,其类型为 pid_t
(无符号整型),进程号的范围:0~32767。进程号总是 唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用。
进程除了 PID
外,还有 PPID
(父进程号),任何进程( 除 init 进程)都是由另一个进程创建。所有进程的祖先进程是同一个进程,它叫做init 进程
,所以,在 Linux 下面所有的进程都由 init 进程
直接或者间接创建。init
进程的PID 为 1, 是内核的第一个启动用户的进程。
缩写 | 中文名称 | 描述 | 获取方式 |
---|---|---|---|
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( )调用的特点是 调用一次,返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程PID
- 在子进程中,fork返回0
- 如果出现错误,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;
}
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进程捕获这个状态
exec( )
使用fork( )函数创建子进程后,子进程通常会调用 exec函数 来执行另外一个程序
当进程调用exec函数时,该进程由新程序完全替换,即用一个可执行程序 代替当前进程的执行映像,而新程序则 从其main函数开始执行。因为调用exec并不创建新进程,所以 前后的进程ID并未改变。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;
}
问题描述
请回答,程序的运行结果是什么?并且分析程序输出这个结果的原因。
程序运行结果
结果分析
- 运行父进程时,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);
}
问题描述
请回答,程序的运行结果是什么?并且分析程序输出这个结果的原因。
程序运行结果
结果分析
-
该代码验证了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]);
}
问题描述
回答:程序执行的结果是什么?请分析改程序的执行过程。
程序运行结果
结果分析
-
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);
}
问题描述
回答:程序执行的结果是什么?请分析改程序的执行过程。
程序运行结果
结果分析
-
在本次程序代码运行过程中,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);
}
问题描述
回答:程序运行的结果是什么?请根据结果结合程序代码分析父进程等待子进程的。
程序运行结果
结果分析
- 由于line34 wait( )的存在,使得父进程暂时处于阻塞状态,需要等待被fork( )出来的子进程执行完毕,才会继续执行父进程的代码。因此,该源代码首先输出了子进程的内容,即连续输出6句“Child process is running”
- 等待子进程结束后,wait( )回收子进程资源,并且将status赋值为子进程退出的状态值,最后将父进程的输出内容打印在屏幕上