欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

程序员文章站 2022-05-05 10:33:38
...

实验1:编写一段程序,使用系统调用fork()创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个出现输出不同的内容。试观察并记录显示结果,并分析原因。

fork()函数说明:

         函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。需要注意的一点:就是调用fork函数之后,一定是两个进程同时执行的代码段是fork函数之后的代码,而之前的代码以及由父进程执行完毕。

fork()返回值意义如下:

=0:在子进程中

>0:在父进程中

<0:创建失败


代码部分:

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


int main(int argc,char *argv[])
{
	pid_t pid1,pid2;        //进程标识符
	pid1 = fork();     //创建一个新的进程
	if(pid1<0)
	{
		printf("创建进程失败!");
		exit(1);
	}
	else if(pid1==0)   //如果pid为0则表示当前执行的是子进程
	{
		printf("子进程1,进程标识符是%d\n",getpid());
	}
	else          //否则为父进程
	{
		pid2 = fork();//創建一個新的進
		if(pid2<0)
		{
			printf("创建进程失败!");
			exit(1);
		}
		else if(pid2==0)   //如果pid为0则表示当前执行的是子进程
		{
			printf("子进程2,进程标识符是%d\n",getpid());
		}
		else          //否则为父进程
		{
			printf("父进程,进程标识符是%d\n",getpid());
		}
	}
	return 0;
}


Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

                                                                         结果如上图

 分析:在调用fork()函数创建子进程后,父子进程的执行顺序由操作系统来决定,相互之间没有任何时序上的关系,所以在我们没有加入进程同步机制的代码的情况下试图靠着调整语句的先后顺序来控制父子进程的先后循序是不可能的,所以在运行得到的结果中,我们看到每次运行的结果都有可能与上次的运行结果不同。

    当然,细心的看官可能会发现:你的程序怎么父进程总是先执行呢?Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

    答:之前说过父子进程的执行顺序完全由操作系统决定,对于我的机器而言,他偏偏是总是父进程先执行,那我有什么办法呢?(实验二就是办法!!),所以可以看到我的程序每次在执行时,总是先打印出父进程,而两个子进程的执行顺序完全时在多执行几次后就可以看到不同的顺序了。Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)




实验二:修改上述程序,每一个进程循环显示一句话。子进程显示“duaghter...”及“son...”,父进程显示“parent...”,观察结果,分析原因。


这里,我在程序的每个进程中使用的for循环,循环显示一句话,循环次数为9次。

代码部分:

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


int main(int argc,char *argv[])
{
	pid_t pid1,pid2;        //进程标识符
	pid1 = fork();     //创建一个新的进程
	if(pid1<0)
	{
		printf("创建进程失败!");
		exit(1);
	}
	else if(pid1==0)   //如果pid为0则表示当前执行的是子进程
	{
		for(int i=0;i<10;i++)
			printf("duaghter...,进程标识符是%d\n",getpid());
	}
	else          //否则为父进程
	{
		pid2 = fork();//創建一個新的進
		if(pid2<0)
		{
			printf("创建进程失败!");
			exit(1);
		}
		else if(pid2==0)   //如果pid为0则表示当前执行的是子进程
		{
			for(int i=0;i<10;i++)
				printf("son...,进程标识符是%d\n",getpid());
		}
		else          //否则为父进程
		{
			for(int i=0;i<10;i++)
				printf("parent...,进程标识符是%d\n",getpid());
		}
	}
	return 0;
}

Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

                                                                     运行结果图

分析:

gcc -o 编译完成后,./+可执行文件名执行后,多次重复执行,观察到每次结果都有所不同,for循环时操作系统的调用次序不同,显示的结果也不同。接着之前的分析,我们在调用fork()函数后可能每次执行都是父或子进程先执行,这也许会给我们制造一些困惑,难道我的程序出错了?难道书上讲错了?

   当然不是,之所以实验一我们总看到父进程先执行,那是因为我们的程序执行太简单了!!!

   每个进程就执行一个printf()这当然不合理,说明不了什么问题,所以基于这个问题,这次我将用一个for循环对每个进程的输出多执行几次,当然我们也可以用while(1)来一直执行,那样结果更加明显,当然最好设置一个键盘响应函数让按下某个键后可以退出。

从我执行得到的结果可以看出,现在,parent,duaghter,son的执行顺序就是随机的, 可能前一秒操作系统还在执行parent进程里面的代码,后一秒他就变了,开始执行son,或者duaghter里面的代码了。

    实验进行到这我们也就可以更加深刻的了解fork()函数的来龙去脉了吧!

想要更加深入的了解fork()函数的使用可以参考网上的各种博客上关于fork()函数的解析!!!Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)


实验三:在调用exec()函数用新的程序替换该子进程的内容,并利用wait()函数来控制进程执行的顺序。调用exit()函数使子进程结束。


在fork后的子进程中使用exec函数族,可以装入和运行其它程序;

exec()函数说明:

实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

(百度百科上解释)为防止大家对exec()函数族理解出错。Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)

这些函数具体用法大家可以亲自尝试一下,网上都有各种说明,在这不做过多说明;


在Linux中使用exec函数族主要有一下两种情况:

      1,当进程认为自己不能再为系统和用户做出任何贡献调用exec函数族让自己重生

      2,进程需要执行另一个程序。

wait()函数:

原理:进程一旦调用了wait,就立即阻塞自己,当分析到当前进程的子进程已经exit,便会收集这个子进程的信息,然后彻底销毁,如果没有找到这样的子进程,就会一直阻塞在这里,直到有一个出现。

  在这里我调用execl()函数列出了路径为/bin/下的文件信息;两个子进程各自调用一次。


代码部分:

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


	int main(int argc,char *argv[])
{
	pid_t pid1,pid2;        //进程标识符
	pid1 = fork();     //创建一个新的进程
	if(pid1<0)
	{
		printf("创建进程失败!");
		exit(1);
	}
	else if(pid1==0)   //如果pid为0则表示当前执行的是子进程
	{
		printf("子进程1,我将调用exec函数,进程标识符是%d\n",getpid());
		execl("/bin/ls","ls","-1","-color",NULL);
		printf("exec failed!\n");
		exit(1);
	}
	else          //否则为父进程
	{
		pid2 = fork();//創建一個新的進
		if(pid2<0)
		{
			printf("创建进程失败!");
			exit(1);
		}
		else if(pid2==0)   //如果pid为0则表示当前执行的是子进程
		{
			printf("进程2,我将调用exec函数,进程标识符是%d\n",getpid());
			execl("/bin/ls","ls","-1","-color",NULL);
			printf("exec failed!\n");
			exit(1);
		}
		else          //否则为父进程
		{
			wait(NULL);
			printf("父进程,进程标识符是%d\n",getpid());
			printf("完成!\n");
			exit(0);
		}
	}
	return 0;
}


运行结果:

Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)


分析:

可以看出在使用了exec()函数后程序使用了ls的命令,列出/bin/目录下的文件信息,执行完execl()函数后,子进程调用exit()函数,退出当前进程,而我们可以发现在使用wait()函数后,父进程永远将在其他的子进程完成之后才执行,所以在输出的结果中我们可以看到最后输出的将是父进程的信息。



实验四:利用Linux的信号量机制实现生产者消费者问题(基于进程)。


目的:利用生产者消费者问题更加深刻的理解进程同步与互斥的实现。

几个函数简介:

pthread_create():

函数简介

  pthread_create是UNIX环境创建线程函数

头文件

  #include<pthread.h>

函数声明

  int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);

返回值

  若成功则返回0,否则返回出错编号

参数

  第一个参数为指向线程标识符的指针。

  第二个参数用来设置线程属性。

  第三个参数是线程运行函数的地址。

  最后一个参数是运行函数的参数。


pthread_join():

函数简介

  函数pthread_join用来等待一个线程的结束。

函数原型为:

  extern int pthread_join __P (pthread_t __th, void **__thread_return);

参数:

  第一个参数为被等待的线程标识符

  第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。

注意

    这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。


signal():

SIGINT这个信号是在用户在控制台输入Ctrl+C的时候进程收到的。  
   
  signal(SIGINT,   &sig_int);这一句指定了收到了SIGINT这个信号以后,处理函数是sig_int  
   

  只要在10秒内输入Ctrl+C,屏幕上会打印出"Catch   a   termination   single."

在程序中我用signal()函数作为结束程序的函数按下Ctrl+C退出程序。


sleep():

就是挂起进程指定的秒数,时间到了返回0。这个函数是我们调节生产者消费者生产和消费能力强弱的函数。

有关信号量sem_t,以及信号量的各个函数的使用的简介可以参考一下博客https://blog.csdn.net/evsqiezi/article/details/8061176


代码部分:


生产者生产过程:

producer()
{
  while(生产未完成)
  {
   .
   .
   .
    生产一个产品;
   P(empty)
   P(mutex);
   送一个产品到有界缓冲区;
   v(mutex);
   v(full);

  }

}

消费者消费过程:

consumer()
{
  while(还要继续消费)
  {
   P(full)
   P(mutex);
   从有界缓冲区取走产品;
   v(mutex);
   v(empty);
   .
   .
   .
   消费一个产品;
  }

}

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<signal.h>


#define producer_N 4//生产者数量
#define consumer_N 5//消费者数量
#define Buffer_N 9//缓冲区大小
int producer_id=0;//生产者ID
int consumer_id=0;//消费者ID
int in=0;//消费者放取产品的位置
int out=0;//消费者放取产品的位置
int Buffer[Buffer_N];//缓冲区
sem_t sem_empty;//同步信号量
sem_t sem_full;//同步信号量
pthread_mutex_t mutex;//互斥信号量
void Signal_print(int signo);//处理信号
void print();  //打印缓冲队列 
void *producer();//生产者
void *consumer();//消费者 
void main()
{
	pthread_t m_producer[producer_N];
	pthread_t m_consumer[consumer_N];
	int i,ret1[producer_N],ret2[consumer_N];
	printf("生产者数目都为4,消费者数目为5,产品缓冲为9,生产者每3秒生产一个产品,消费者每6秒消费一个产品,Ctrl+c退出程序\n");  
	if(signal(SIGINT,Signal_print)==SIG_ERR)
		printf("error signal!\n");


	int ru1=sem_init(&sem_empty,0,Buffer_N);//初始化有界缓冲区大小
	int ru2=sem_init(&sem_full,0,0);//初始化满缓冲区大小
	if(ru1||ru2)
	{
		printf("error init signal!");
		exit(0);
	}
	int ru3=pthread_mutex_init(&mutex,NULL);
	if(ru3)
	{
		printf("error init thread!\n");
		exit(1);
	}
	// 创建生产者线程  
	for(i = 0; i < producer_N; i++)
	{  
		ret1[i]= pthread_create(&m_producer[i], NULL, producer, (void *) (&i));  
		if(ret1[i] != 0) {  
			printf("生产者%d线程创建失败!\n", i);  
			exit(1);  
		}  
	}  
	//创建消费者线程  
	for(i = 0; i < consumer_N; i++) 
	{  
		ret2[i]= pthread_create(&m_consumer[i], NULL, consumer, NULL);  
		if(ret2[i] != 0) {  
			printf("消费者%d线程创建失败!\n", i);  
			exit(1);  
		}  
	}  
	//等待线程销毁  
	for(i = 0; i < producer_N; i++)
		pthread_join(m_producer[i], NULL);  
	for(i = 0; i < consumer_N; i++)
		pthread_join(m_consumer[i], NULL); 
	exit(0);  


}


void Signal_print(int signo)//处理信号
{
	printf("%d exit applications!\n",signo);
	exit(0);
}


void *producer()//生产者
{
	int id = producer_id;
	while(1)//生产未完成
	{
		sleep(3);//调节生产者和消费者生产消费的速度
		sem_wait(&sem_empty);//p(empty)操作
		pthread_mutex_lock(&mutex);//p(mutex)操作
		//完成生产者的操作 
		in%= Buffer_N;
		printf("生产者%d将产品放入缓冲区队列第%d号    ",id,in+1);
		Buffer[in]=1;
		print();
		++in;
		pthread_mutex_unlock(&mutex);//v(mutex)操作
		sem_post(&sem_full);//v(full)操作


	}
}


void *consumer()//消费者
{
	int id = ++consumer_id;
	while(1)//消费未完成
	{
		sleep(6);//调节生产者和消费者生产消费的速度
		sem_wait(&sem_full);//p(full)操作
		pthread_mutex_lock(&mutex);//p(mutex)操作
		//完成消费者的操作
		out%= Buffer_N;
		printf("消费者%d从缓冲区队列第%d号取走产品    ",id,out+1);
		Buffer[out]=0;
		print();
		++out;
		sem_post(&sem_empty);//v(empty)操作
		pthread_mutex_unlock(&mutex);//v(mutex)操作
	}
}


// 打印缓冲情况  
void print() {  
	int i;  
	printf("产品队列为");  
	for(i = 0; i <Buffer_N; i++)  
		printf("%d", Buffer[i]);  
	printf("\n");  
} 

运行结果:

Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作) 


分析:

初始状态缓冲区为空,此时只允许生产者生产资源放入缓冲区,而消费者试图取缓冲区产品时,将进入等待队列,等待资源释放,在等待队列中的第一个进程将得到资源的控制权。当缓冲区产品满了,P(empty)将拒绝生产者向缓冲区输送资源。

     生产者每隔三秒向缓冲区输送产品,缓冲区满了,则等待消费者消费产品后再将产品输送进去;消费者每隔六秒向缓冲区取得产品,缓冲区为空,则等待生产者输送产品后再消费产品。

    如果想要退出程序,按下ctrl+c 即可!


    (本实验为单缓冲区)

注意:在编译时在末尾加上-lpthread,这样才能正常编译!!


  最后,祝大家好好学习,天天向上!!!Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)