Linux操作系统实验一:进程管理
题目:
【实验目的】
掌握 Linux 操作系统的使用方法;掌握 Linux 环境下 C 程序开发方法;加深对进程概念的理解,进一步认识并发执行的实质;学习通过进程执行新目标程序的方法;了解 Linux 系统中进程信号处理的基本原理;掌握 make工具的使用方法。
【实验预备内容】
(1)学习 Linux 操作系统使用方法;C 语言编程方法,Linux 下编译、调试、运行 C 程序的方法;理解进程与程序的区别,并发执行的概念。
(2)理解系统调用 fork( )的作用,分析进程的创建过程。
(3)阅读相关章节参考资料:make 的使用;进程控制;信号。加深对进程管理概念的理解。
【实验内容】
阅读基础篇、进程控制、信号等章节的相关参考资料,分析示例程序代码,熟悉实验环境,用 C 语言编写实现包含以下四个要求的程序。
- 编译使用 make 工具;
- 代码使用 fork 函数;
- 代码使用 exec 函数;
- 代码使用 signal 函数或 sigaction 函数。
学号:021900208 姓名:高旭 专业:计算机科学与技术 班级:02
一、实验环境:
Oracle VM VirtualBox、Ubuntu(64-bit)
二、实验内容:
代码部分:
gx1.c
/*gx1.c*/
/*
代码先后调用两次fork,创建两个子进程,
第一个子进程调用execv实现编译,第二个子进程调用execl执行程序
父进程在这个过程中会堵塞自己,等待捕获成为僵尸进程的子进程。
*/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(void)
{
char *arg1[]={"gcc","-o","gx2","gx2.c",NULL};
pid_t pid,pr; /*进程标识符*/
if((pid=fork())<0) /*创建第一个子进程*/
{
perror("fork"); /*错误处理*/
printf("第一个子进程创建失败。");
exit(0);
}
if(pid==0)
{
sleep(3);
printf("第一个子进程创建成功\n");
if((execv("/bin/gcc",arg1))==-1) /*转去编译gx2.c并判断是否出错*/
{
perror("execv"); /*错误处理*/
printf("第一次尝试调用进程失败\n");
exit(0);
}
}
if((pid=fork())<0) /*创建第二个子进程*/
{
perror("fork"); /*错误处理*/
printf("第二个子进程创建失败\n");
exit(0);
}
else if(pid==0)
{
sleep(5);
printf("第二个子进程创建成功\n");
if((execl("/home/gaoxu/ex1/gx2","",NULL))==-1) /*转去执行程序gx2并判断是否出错*/
{
perror("execl");
printf("第二次尝试调用进程失败\n");
exit(0);
}
}
else
{
printf("标识符为%d的父进程正在等待捕获僵尸进程\n",pid);
pr=wait(NULL); /*父进程阻塞自己,收集僵尸进程信息*/
printf("父进程成功捕获僵尸进程,标识符为%d\n",pr);
}
return 0;
}
gx2.c
/*gx2.c*/
/*代码实现当没有接受到SIGINT或SIGQUIT信号时,执行死循环*/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int wait_mark=1;
void hangup()
{
while(wait_mark==1);
}
void Newhandler1(int iSignNo,siginfo_t *pSignInfo, void *pReserved)
{
printf("接受到SIGINT信号,进入信号处理函数Newhandler1,signo:%d.\n",iSignNo);
wait_mark=0; /*终止死循环*/
printf("离开信号处理函数Newhandler1,signo:%d\n",iSignNo);
}
void Newhandler2(int iSignNo,siginfo_t *pSignInfo, void *pReserved)
{
printf("接受到SIGQUIT信号,进入信号处理函数Newhandler2,signo:%d.\n",iSignNo);
wait_mark=0; /*终止死循环*/
printf("离开信号处理函数Newhandler1,signo:%d\n",iSignNo);
}
int main(void)
{
struct sigaction act1,act2; /*包含信号处理动作的结构体*/
act1.sa_sigaction=Newhandler1; /*指定信号处理函数*/
act2.sa_sigaction=Newhandler2;
act1.sa_flags=SA_SIGINFO; /*表明信号处理函数由sa_sigaction指定*/
act2.sa_flags=SA_SIGINFO;
sigemptyset(&act1.sa_mask); /*信号集处理函数,将act.sa_mask所指向的信号集清空,*/
sigemptyset(&act2.sa_mask); /*即不包含任何信号*/
sigaction(SIGINT,&act1,NULL); /*注册SIGINT信号*/
sigaction(SIGQUIT,&act2,NULL); /* 注册SIGQUIT信号*/
hangup(); /*等待信号*/
pid_t pid=getpid(); /*获取进程标识*/
printf("标识符为%d的子进程已经被杀死\n",pid);
exit(0);
return 0;
}
结果截图:
三、实验总结:
1.程序思想:
流程图:
如上图,本次实验编写了gx1.c与gx2.c文件,
①gx1.c中使用fork函数,sleep函数,exec函数族,wait函数
实现了
(1)fork生成一个子进程,该子进程调用execv函数实现对gx2.c的编译
(2)再次fork生成一个子进程,调用execl函数实现对gx2程序的运行,其中使用sleep函数实现同步,避免出现先调用运行程序命令再调用gcc编译命令。
(3)父进程使用wait函数等待第一个子进程生命周期结束,捕获僵尸进程。
②gx2.c中使用sigaction函数,定义hangup死循环函数、Newhandler1和Newhandler2信号处理函数。
实现了
(1)在没有收到SIGINT信号与SIGQUIT信号时,执行hangup函数进入死循环。
(2)接受到SIGINT信号或SIGQUIT信号,信号处理函数将死循环参数清零,杀死进程。
2.本次实验自学的知识:
①fork函数
②exec函数族
③wait函数
④signal函数
⑤sigaction函数
⑥make工具
⑦Linux的并发执行
⑧Linux的进程管理与通信
3.遇到的问题与解决:
①将Makefile.txt工具拖入虚拟机,使用make命令出现报错make: *** No targets specified and no makefile found. Stop.
解决:通过查询资料得知,该报错是因为make命令找不到名称为Makefile或是makefile的文件。
首先尝试指定文件名为Makefile再次报错,猜测可能是txt文件不可以做makefile文件。
最后使用vi Makefile命令直接在Linux虚拟机中编写Makefile文件。
②使用exec函数不能实现编译其他.c文件并执行。
解决:查阅资料得知,
调用gcc编译命令pathname路径应该指定为根目录gcc文件所在位置,本次实验为/bin/gcc。
使用execl调用可执行程序应该给出完整的全路径:/home/gaoxu/ex1/gx2,并且即便该程序无参数也不能直接使用NULL结尾,应该在在NULL前加入“”空字符。
③execl调用程序gx2,但gx2无法接受到键盘给出的ctrl+c或ctrl+\的信号。
解决:在长时间尝试无果后询问老师,猜测因为键盘触发的ctrlc信号或ctrl\信号只能发给当前运行的前台进程,分析可能原因是不是程序中调用的程序会转为后台运行中,使用ps可知gx2确实已经在系统中运行,在终端使用kill命令发送SIGINT信号或SIGQUIT信号,gx2进程也能正常被杀死。