操作系统课程设计四、进程管理#
实验内容###
父进程使用系统调用pipe()建立一个管道,然后使用系统调用fork()创建两个子进程:子进程1和子进程2。
子进程1每隔1秒通过管道向子进程2发送数据:I send message x times.(x初值为1,以后发送一次后做加一操作)子进程2从管道读出信息,并显示在屏幕上。
父进程用系统调用signal()来捕捉来自键盘的中断信号SIGINT(即按Ctrl+C键,);当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后分别输出如下信息后终止:
Child Process 1 is killed by Parent!
Child Process 2 is killed by Parent!
父进程等待两个子进程终止后,释放管道并输出如下的信息后终止
Parent Process is Killed!
实验功能及设计思路###
程序功能:
父进程创建两个子进程:子进程1和子进程2
子进程1每隔1秒通过管道向子进程2发送数据:I send message x times.子进程2从管道读出信息,并显示在屏幕上
子进程捕捉到信号后分别输出如下信息后终止: Child Process 1 is killed by Parent! Child Process 2 is killed by Parent!父进程等待两个子进程终止后,释放管道并输出如下的信息后终止: Parent Process is Killed!
设计思路:
-
首先定义两个数组,用来消息传送。
-
通过系统调用pipe()实现管道,管道是半双工的,数据只能向一个方向流动(用lockf 或关闭文件);需要双向通信时,需要建立起两个管道;数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。管道通过系统调用 pipe()来实现,函数的功能和实现过程如下。原型:int pipe( int fd[2] )返回值:如果系统调用成功,返回 0。如果系统调用失败返回-1: errno = EMFILE (没有空闲的文件描述符)。fd[0] 用于读取管道,fd[1] 用于写入管道。
-
利用fork()创建两个进程,如果 fork()调用成功,它向父进程返回子进程的 pid,并向子进程返回 0,即 fork() 被调用了一次,但返回了两次。创建进程之后,对于子进程1,关闭读(fd[0]),使用write实现消息传递。对子进程2,关闭写(fd[1]),使用read读到消息,打印到屏幕。
-
每一个进程都在各自的while(1)循环中,当信号发出,执行相应程序代码,退出当前进程。
-
加入错误处理机制,每次fork()都判断返回值是否非为-1。同时根据返回值是否大于0,来判断是父进程还是子进程。
-
exec()系列系统调用 exec()系列,也可用于新程序的运行。fork()只是将父进程的用户级上下文拷贝到新进程中,而 exec()系列可以将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上,以更改新进程的用户级上下文。exec()系列中的系统调用都完成相同的功能,它们把一个新程序装入内存,来改变调用进程的执行代码,从而形成新进程。如果exec()调用成功后,没有任何数据返回,这与 fork()不同。exec()系列调用在 LINUX 系统库 unistd.h 中,共有 execl、execlp、execv、execvp 五个,其基本功能相同,只是以不同的方式来给出参数。
-
exit()终止进程的执行。系统调用格式:Void exit(status) 其中,status 是返回给父进程的一个整数,以备查考。为了及时回收进程所占用的资源并减少父进程的干预,LINUX/LINUX 利用 exit()来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条 exit(),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。如果调用进程在执行 exit()时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。
-
signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。
源代码##
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
pid_t pid1, pid2;//进程类型定义
int fd[2];
int cnt=0;
void handler(int signo){
if(signo==SIGINT){
kill(pid1,SIGUSR1);//发出信号
kill(pid2,SIGUSR2);
}
if(pid1==0&&signo==SIGUSR1){
printf("
Child Process 1 is killed by Parent!
");
exit(0);
}
if(pid2==0&&signo==SIGUSR2){
printf("
Child Process 2 is killed by Parent!
");
exit(0);
}
}
int main()
{
char buffer[1<<8];
char msg[1<<8];
memset(buffer,0,sizeof(buffer));
memset(msg,0,sizeof(msg));
if(pipe(fd)<0){
perror("pipe");//perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)
exit(1);
}
signal(SIGINT,handler);//当接收到一个类型为sig的信号时,就执行handler 所指定的函数
pid1=fork();
if(pid1==0){
//子进程
signal(SIGINT,SIG_IGN);//屏蔽结束
signal(SIGUSR1,handler);
while(1){
close(fd[0]);//关闭读
sprintf(msg,"I send message %d times
",++cnt);
write(fd[1],msg,strlen(msg));
sleep(1);
}
}
else if(pid1>0){
pid2=fork();
if(pid2==0){
signal(SIGINT,SIG_IGN);
signal(SIGUSR2,handler);
while(1){
close(fd[1]);//关闭写
read(fd[0],buffer,sizeof(buffer));
printf("%s
",buffer);
}
}
//等两个子进程结束
waitpid(pid2, NULL, 0);
waitpid(pid1, NULL, 0);
close(fd[0]);
close(fd[1]);
printf("Parent Process is killed!
");
}
else{
perror("fork");
exit(1);//不正常退出
}
return 0;
}