实验目的
1、熟悉LINUX的常用基本命令,学会编译、调试C程序。
2、认识进程的创建、控制、互斥及守护进程的建立。
3、了解和熟悉LINUX支持的信号量机制、管道机制、消息通信机制及共享存储区机制。
实验内容
1.进程的创建实验
1-1. 调用fork()创建两个子进程,父进程显示’a’,子进程分别显示字符’b’和字符’c’。观察结果,分析原因。
#include <stdio.h>
#include <unistd.h> // fork
int main()
{
int p1,p2;
while((p1=fork())== -1); /*create sonProcess-p1*/
if (p1==0){ /*current sonProcess is p1*/
putchar('b');
} else {
while((p2=fork())== -1); /*create sonProcess-p1*/
if(p2==0) putchar('c'); /*current sonProcess is p2*/
else putchar('a');
}
}
分析:
例如:abc / acb,父进程创建子进程后,子进程并未立即执行完,父进程比两个子进程先行执行完,输出a,紧接两个子进程输出b、c或c、b;
例如:bac / bca,父进程创建子进程b后,子进程b优先执行完,先输出b,c而后父进程和子进程c执行结束后a、c或c、a;
总之,输出结果不确定,取决于进程的调度顺序和时间片分配方式。
1-2. 修改上述程序,子进程显示’daughter …‘及’son ……’,父进程显示 ‘parent ……’,观察结果,分析原因。
#include <stdio.h>
#include <unistd.h> // fork
int main(){
int p1,p2,i;
while((p1=fork())== -1); /*create sonProcess-p1*/
if (p1==0)
for(i=0;i<10;i++)
printf("daughter %d\n",i);
else
{
while((p2=fork())== -1); /*create sonProcess-p2*/
if(p2==0)
for(i=0;i<10;i++)
printf("son %d\n",i);
else
for(i=0;i<10;i++)
printf("parent %d\n",i);
}
}
分析:
同第一问类似,可以看到结果非常随机,也很好地证明了进程的并发性。另外值得注意的是,函数printf( )在输出字符串时不会被中断,因此,字符串内部字符顺序输出不变。
2.进程的控制实验
用fork( )创建一个进程,再调用execl( )用新的程序替换该子进程的内容,并利用wait( )来控制进程执行顺序
#include<stdio.h>
#include<stdlib.h> /*exit()*/
#include<unistd.h> /*fork()*/
#include<sys/wait.h> /*wait()*/
int main(){
int pid;
pid=fork();
switch(pid){
case -1: /*create fail*/
printf("fork fail\n");
exit(1);
case 0: /*son*/
execl("/bin/ls","ls","-l","-color",NULL);
printf("exec fail\n");
exit(1);
default: /*parent*/
wait(NULL);
printf("ls completed!\n");
exit(0);
}
}
3.进程的互斥实验
利用lockf()实现进程间的互斥
4.守护进程实验
利用管理员权限创建日志文件/var/log/Mydaemon.log,每分钟向其写入时间戳。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
int main(){
FILE *fp; //建立文件指针
fp = fopen("/var/log/Mydaemon.log","a");
setlinebuf(fp); //设置行缓冲以确保内容立即写入文件
pid_t pid; //定义守护进程
pid = fork();
// 如果fork返回值大于0,表示当前进程是父进程,此时需要退出程序
if (pid > 0) {
printf("Daemon on duty!\n");
exit(0);
}
// 如果fork返回值小于0,表示出现错误,此时需要退出程序
else if (pid < 0) {
printf("Can't fork!\n");
exit(-1);
}
// 当前进程为子进程,一直循环执行,每隔10秒向日志文件中写入当前时间戳
while (1) {
if (fp >= 0) {
sleep(10);
printf("Daemon on duty! ");
time_t t; //建立time_t格式变量
t = time(NULL);
printf("The current time is %s\n",ctime(&t));
}
}
fclose(fp);
}
5.信号机制实验
编写程序:用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parent process is killed!
分析利用软中断通信实现进程同步的机理。
# include<stdio.h>
# include<signal.h>
# include<unistd.h>
# include<sys/wait.h>
#include<stdlib.h>
int wait_mark;
void waiting(){
while(wait_mark!=0);
}
void stop(){
wait_mark=0;
}
int main(){
int p1, p2;
signal(SIGINT,stop); //signal()初始位置
while((p1=fork())==-1);
if(p1>0){
while((p2=fork())==-1);
if(p2>0){
wait_mark=1;
waiting( );
wait(0);
wait(0);
printf("parent process is killed!\n");
exit(0);
}else {
wait_mark=1;
waiting( );
lockf(1,1,0);
printf("child process 2 is killed by parent!\n");
lockf(1,0,0);
exit(0);
}
} else{
wait_mark=1;
waiting( );
lockf(1,1,0);
printf("child process 1 is killed by parent!\n");
lockf(1,0,0);
exit(0);
}
}
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/b264f815ae1c4ea69adeb2f63a452b58.png)
分析:
wait_mark 变量用于实现进程间的同步。当 wait_mark 为 0 时,父进程将继续运行,否则它会阻塞并等待子进程完成。因此,在父进程中,它将 wait_mark 设置为 1 ,然后在 waiting() 函数中轮询变量 wait_mark 的值,直到 wait_mark 变为 0 。在子进程中,当它们完成任务后,它们将 wait_mark 设置为 0 ,从而使父进程结束等待,并继续执行下一步操作,从而实现进程同步。
## 6.进程的管道通信实验
**编写程序实现进程的管道通信。用系统调用pipe()建立一管道,二个子进程P1和P2分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。**
```c++
# include<unistd.h>
# include<signal.h>
# include<stdio.h>
# include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
int pid1,pid2;
int main(){
int fd[2]; //打开文件的文件描述符
char OutPipe[100],InPipe[100]; //存储要写入管道的字符串
pipe(fd);//创建无名管道
while((pid1=fork())==-1); //用fork函数创建子进程
if(pid1==0){ //子进程1号
lockf(fd[1],1,0);
sprintf(OutPipe,"Child process 1 is sending a message!"); //存储字符串
write(fd[1],OutPipe,50);//将字符串写入管道
sleep(5);
lockf(fd[1],0,0);
exit(0);
}else{
while((pid2=fork())==-1); //子进程2号
if(pid2==0){
lockf(fd[1],1,0);
sprintf(OutPipe,"Child process 2 is sending a message!");
write(fd[1],OutPipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}else{ //父进程读
read(fd[0],InPipe,50); //将字符串1从管道中读出到inpipe
printf("%s\n",InPipe); //打印inpipe内数据
wait(0);
read(fd[0],InPipe,50); //将字符串2从管道中读出到inpipe
printf("%s\n",InPipe);
exit(0);
}
}
return 0;
}
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/c0db777648f94c9898f06be20f413bb7.png)
## 7.消息的接收与发送实验
**消息的创建、发送和接收。使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序。**
```c++
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSGKEY 75 /*定义关键词MEGKEY*/
struct msgform /*消息结构*/
{
long mtype;
char mtexe[1030]; /*文本长度*/
}msg;
int msgqid,i;
void client(){
int i;
msgqid=msgget(MSGKEY,0777); //创建消息队列
for(i=10;i>=1;i--)
{
msg.mtype=i;
printf("(client)sent\n");
msgsnd(msgqid,&msg,1024,0); //发送消息msg入msgid消息队列
}
exit(0);
}
void server(){
msgqid=msgget(MSGKEY,0777|IPC_CREAT);
do
{
msgrcv(msgqid,&msg,1030,0,0); //从队列msgid接受消息msg
printf("(server)receive\n");
}while(msg.mtype!=1); //消息类型为1时,释放队列
msgctl(msgqid, IPC_RMID,0); //消除消息队列的标识符
exit(0);
}
int main(){
if(fork()) server (); //父进程
else client (); //子进程
wait(0);
wait(0);
}
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/e55be47f4ff64ad296a59fcdd7ae498a.png)
## 8.共享存储区实验
**编制一长度为1k的共享存储区发送和接收的程序。**
````c++
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<wait.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<sys/ipc.h>
#include <sys/shm.h>
#define SHMKEY 75 /*定义共享区关键词*/
int shmid,i;
int *addr;
void client(){
int i;
shmid=shmget(SHMKEY,1024,0777); /*获取共享区,长1024,关键词SHMKEY*/
addr=shmat(shmid,0,0); /*共享区起始地址为addr*/
for(i=9;i>=0;i--)
{
while(*addr!= -1);
printf("(client)sent\n"); /*打印(client)sent*/
*addr=i; /*把i赋给addr*/
}
exit(0);
}
void server(){
shmid=shmget(SHMKEY,1024,0777|IPC_CREAT); /*创建共享区*/
addr=shmat(shmid,0,0); /*共享区起始地址为addr*/
do
{
*addr=-1;
while(*addr==-1);
printf("(server)received\n"); /*服务进程使用共享区*/
}
while(*addr);
shmctl(shmid,IPC_RMID,0);
exit(0);
}
int main(){
if(fork()) server();
if(fork()) client ();
wait(0);
wait(0);
}
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/86cc21ba138d42be9c8d626e01eabd39.png)
# 实验分析与思考
## (一)进程的创建实验
**1-1.系统是怎么样创建进程的**
进程执行fork函数后,初始化新PCB,同时给新进程分配资源(分配存储数据和代码的内存空间),保存当前进程执行的上下文信息,并复制到新进程中,当所有准备工作完成后,操作系统会将控制权转移到新的进程,以便执行新进程。
**1-2.当首次调用新创建进程时,其入口在哪里?**
fork()调用一次,但返回两次,两次返回区别在于:子程序返回值是0,父进程返回值是子进程的ID。调用成功后,父子进程并发执行相同的代码,因此新进程是从fork()后的语句开始执行(也即新进程调用的入口)。
## (二)进程的控制实验
**2-1. 编写一hello程序,实现输出“hello,姓名”。将hello替代上述的execl 中运行的ls命令。**
![在这里插入图片描述](https://img-blog.csdnimg.cn/e1c32a3aef1445a98c04ea8b04b61a07.png)
```c++
#include<stdio.h>
#include<stdlib.h> /*exit()*/
#include<unistd.h> /*fork()*/
#include<sys/wait.h> /*wait()*/
int main(){
int pid;
pid=fork();
switch(pid){
case -1: /*create fail*/
printf("fork fail\n");
exit(1);
case 0: /*son*/
execl("/home/lhj/osTest1/hello.out","hello",NULL);
printf("exec fail\n");
exit(1);
default: /*parent*/
wait(NULL);
printf("ls completed!\n");
exit(0);
}
}
```
**2-2.什么是进程同步?wait( )是如何实现进程同步的?**
进程同步:保证多个进程在执行过程中相互协调和同步,具体来说,在并发执行的多个进程中,如果一个进程的结果会影响到另一个进程,那么需要保证先执行完一个进程,得到相应结果,再执行另一个进程,保证一定的先后执行顺序。
wait()可以让父进程等待子进程执行完毕后再继续执行。wait()挂起父进程后,直到它的某个子进程结束了执行或收到了某个信号,才会返回进程的pid并且释放子进程占用的系统资源。
## (四)守护进程实验
**4-1.语句“ setlinebuf(fp); //设置行缓冲”起到什么作用?如果没有该语句,程序的执行结果会怎样?**
每次输出完一行数据(即在输入包含'\n'的字符后),就会刷新缓冲区,将缓冲区中的数据写入到要输出的文件中;而不是等到缓冲区满了或者程序结束时再将数据写入文件。如果不写该语句,数据不能及时写入文件中。
## (五)信号机制实验
**5-1.如何修改程序才能得到以下结果**
Child process1 is killed by parent!
Child process2 is killed by parent!
Parent process is killed!
把signal()函数放在程序前面部分执行,让父进程捕捉由键盘发来的中断信号,当系统捕捉到中断信号后,调用预置的stop函数,子进程捕捉到信号后,输出信息并终止,父进程等待两个子进程结束后,也输出信息并终止。
![在这里插入图片描述](https://img-blog.csdnimg.cn/73b340b615f44e51a32f75c9a0878fd3.png)
**5-2.该程序段前面部分用了两个wait(0),它们起什么作用?**
确保父进程等待2个子进程都结束后再执行。
## (六)进程的管道通信实验
**6-1. 程序中的sleep(5)起什么作用?**
休眠5s,防止写冲突
**6-2. 怎样保证先child1进程,再child2进程?**
对于创建两个子进程的顺序,父进程通过两个while循环,保证先创建子进程1,后创建子进程2。在子进程进行写入操作时,加入锁机制保证互斥控制,具体来说,子进程child1进行写入操作时,先给管道上锁( lockf(fd[1],1,0) ),执行完写入操作再解锁( lockf(fd[1],1,0) ),确保当前进程操作完成后再释放管道资源。接着再轮到child2进行写入操作,从而保证两个子进程的写顺序。
**6-3. 子进程1和2为什么也能对管道进行操作?**
Fork()创建两个子进程,子进程会继承父进程所创建的管道。