操作系统课程设计实验报告
设计题目一:Linux进程线程控制
——以生产者消费者为例实现进程线程控制
——by
一、设计题目要求
加深理解进程和程序、进程和线程之间的联系与区别;
深入理解进程及线程的重要数据结构及实现机制;
熟悉进程及线程的创建、执行、阻塞、唤醒、终止等控制方法;
学会使用进程及线程开发应用程序。
二、程序设计思路及流程图
程序功能简介:
生产者功能描述:在同一个进程地址空间内执行两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。
消费者功能描述:消费者线程从缓冲区获得物品,然后释放缓冲区,当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。
程序设计思路:
设计了两个主要函数:生产者函数、消费者函数
设计了三个信号量:full信号量,判断缓冲区是否有值,初值为0;
empty信号量,判断缓冲区是否有空缓冲区,初值为缓冲区数;
mutex信号量作为互斥信号量,用于互斥的访问缓冲区。
生产者函数通过执行P操作信号量empty减1,判断缓冲区是否有空。有空则互斥的访问缓冲区病放入数据,然后释放缓冲区,执行V操作,信号量full加1.
消费者函数执行P操作,信号量full减1,判断是否有数据,有则互斥的访问缓冲区并取走数据,然后释放缓冲区,执行V操作,empty信号量加1。
程序流程图:
见附录A
三、涉及的背景知识及所用函数简介
1、fork 函数
函数原型 :pid_t fork(void)
头文件 :unistd.h
作用 :创建一个子进程
返回值 :成功后父进程返回子进程的PID号,子进程返回0;失败返回-1
2、pthread_create 函数
函数原型 :int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_rtn) (void*),void * arg)
头文件 :pthread.h
作用 :创建一个线程
参数 :thread 待创建线程的id指针
pthread_attr_t 创建线程时的线程属性
*start_rtn(void * ) 返回值是void*类型的指针函数
arg 函数start_rtn的参数
返回值 :成功返回0;失败返回错误编号
3、pthread_join 函数
函数原型 :int pthread_join( pthread_t thread, void ** rval_ptr)
头文件 :pthread.h
作用 :1)调用者将挂起并等待指定线程终止
2)当新线程调用pthread_exit()退出或者return时,进程中的其他线 可通过pthread_join()获得进程的退出状态
参数 :thread 线程的ID
rval_ptr 线程的返回状态
返回值 :成功返回0;失败返回错误编码
4、wait 函数
函数原型 :pid_t wait(int *status)
头文件 :sys/wait.h
作用 :调用者将挂起并等待子进程终止
参数 :status 进程的返回状态
返回值 :子进程的ID号
5、sem_wait 函数
函数原型 :int sem_wait(sem_t*sem)
头文件 :semaphore.h
作用 :从信号量的值减去一个“1”,但它永远会先等待该信号量为一个
零值才开始做减法
返回值 :所有的函数成功返回0,错误的话信号量的值不改动,返 回-1,error设定来标识错误
6、sem_post 函数
函数原型 :int sem_post(sem_t*sem)
头文件 :emaphore.h
作用 :给信号量的值加上一个“1”
返回值 :所有的函数成功返回0,错误的话信号量的值不改动,返 回-1,error设定来标识错误
7、sem_init 函数
函数原型 :int sem_init(sem_t*sem, int pshared, unsigned int value);
头文件 :emaphore.h
作用 :初始化一个定位在sem的匿名信号量
返回值 :成功是返回0;错误时,返回—1,并把error设置为合适的值。
8、sem_destroy 函数
函数原型 :int sem_destroy(sem_t*sem)
头文件 :emaphore.h
作用 :销毁有sem指向的匿名信号量
返回值 :成功是返回0;错误时,返回—1,并把error设置为合适的值。
9、signal 函数
函数原型 :void (*signal(int signum,void(*handler)(int)))(int);
头文件 :signal.h
作用 :设置某一信号的对应动作
参数 :sinnum指明了所要处理的信号类型
handler描述了与信号关联的动作
返回值 :返回先前的信号处理函数指针,如果有错误则返回 SIG_ERR(-1)。
10、alarm 函数
函数原型 :unsigned int alarm (unsigned int seconds);
头文件 :unistd.h
作用 :在进程中设置一个定时器,当定时器指定的时间到时,它向进程 发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作 是终止调用该alarm函数的进程。
参数 :seconds 制定秒数
返回值 :成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返 回上一个闹钟时间的剩余时间,否则返回0。出错:-1
11、getpid 函数
函数原型 :int_getpid(void)
头文件 :unistd.h
作用 :取得进程识别代码
返回值 :目前进程的进程识别码
12、srand 函数
函数原型 :void srand(unsigned seed);
头文件 :stdlib.h
作用 :产生伪随机数序列
参数 :seed 改变系统提供的种子植
13、Pthread_setcancel state 函数
函数原型 :int pthread_setcancelstate(int state, int *oldstate)
作用 :设置本线程对cancel信号的反应,state有两种值,分别表示收到 信号后设为canceled状态和忽略cancel信号继续运行;oldstate如果 不为null则存入原来的cancel状态以便恢复。
四、程序所用数据结构简介
int num_producer ,num_consumer :宏定义中生产者,消费者的数量
int BUFFER_SIZE :缓冲区大小
int NUM :产品编号
int total_producer,total_consumer :每5秒对生产者和消费者数量进行统计
int buffer[BUFFER_SIZE] :缓冲区模型
int nextp, nextc :生产者,消费者的指针
pthread_t thrreads_p[100],threads_c[100] :生产者,消费者线程
sem_t empty,full, mutex :信号量,以使生产者之间和消费者之间,以及二者之间的互斥
void handler() :用来循环输出生产者,消费者的数量
五、程序源代码
见附录B
六、调试方案及调试过程记录与分析
输入文件选取:
本程序无需输入,只需按照需要更改生产者,消费者,缓冲区的数量和大小即可。
测试结果、调试过程:
第一次测试结果:显示很多函数文件未定义。
第一次调试:在makefile文件开头加vpath命令,来指明用户自己所定义的头文件的位置,同时在每个用到用户自己定义头文件的.c文件的gcc-c一行加上-I./include。
第二次测试结果:头文件被包含进去,但NUM变量出现重复定义错误。
第二次调试:在.h文件中声明全局变量,而全局变量的赋值放在了主函数producer_consumer.c中。
第三次测试结果:使用1个生产者,1个消费者,2块缓冲区,此时无语法错误,但是改变之前的数据为3,3,20之后,生产者和消费者出现次序并没有体现同步与互斥。
第三次调试:使用函数pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL)取消线程,和srand函数,使生产者和消费者可以同步和互斥。
第四次测试结果:程序可以正确执行,无语法错误,并且可以实现同步和互斥。
七、程序运行结果分析
通过四次测试调试运行,程序可以正确执行,可以实现生产者、消费者的同步与互斥,以及对缓冲区的互斥使用。
程序源代码:
源文件名:producer_consumer.c
内容:
/*
**************************************************************************************
*Project/File :producer_consumer.c
*By :
*Mail :
*Status :finished
*version :2.0
*Created Time :2014年09月24日星期三17时45分50秒
**************************************************************************************
*Note:
*系统里有若干个合作的进程/线程,其中n个生产者线程,m个消费者线程,以及p块缓冲区。
*任何一个生产者都可以将自己的产品存入缓冲区的任何一个位置;
*任何一个消费者都可以将缓冲区内的一个产品取出;
*生产者源源不断地生产并存入产品;
*消费者周而复始地从缓冲区中取出产品将其消费掉;
**************************************************************************************
*/
#include"producer_consumer.h"
void handler()
{
printf("total_producer = %d, total_consumer = %d\n", total_producer, total_consumer);
signal(SIGALRM, handler); //让内核做好准备,一旦接受到SIGALARM信号,就执行 handler;
alarm(5); //闹钟设为5秒;
}
int main()
{
NUM=1;
total_producer = 0;
total_consumer = 0;
nextp=0;
nextc=0;
int p1;
p1 = fork();
if(p1)
{
//parent
printf("This is parent(%d)!\n", getpid());
}
else
{
//child
int i;
handler(); //每隔5秒钟统计一下生产的产品数和消费的产品数;
sem_init(&empty,0,BUFFER_SIZE); //初始化信号量;
sem_init(&full,0,0);
sem_init(&mutex,0,1);
for(i=0;iBUFFER_SIZE) NUM=1; //如果大于20,NUM重新为1 ;
buffer[nextp]=(NUM++);
printf("生产者编号:%d\t指针:%d\t\n", (int)tid, nextp);
nextp=(nextp+1) % BUFFER_SIZE; //生产者指针后移,指向下一个位置
total_producer++;
printf("缓冲区:");
for(i=0;i