操作系统课程设计实验报告
设计题目一: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;i<BUFFER_SIZE;i++) //清空缓冲区; buffer[i]=0; for(i=0;i<num_producer;i++) //创建生产者,消费者线程; pthread_create(&threads_p[i],NULL,producer_thread,(void *)(i+1)); for(i=0;i<num_consumer;i++) pthread_create(&threads_c[i],NULL,consumer_thread,(void *)(i+1)); for(i=0;i<num_producer;i++) //销毁线程; pthread_join(threads_p[i],NULL); for(i=0;i<num_consumer;i++) pthread_join(threads_c[i],NULL); sem_destroy(&full); sem_destroy(&empty); //销毁信号量; sem_destroy(&mutex); } return 0; } 源文件名:producer.c 内容: /* ************************************************************************************** *Project/File :producer.c *By : *Mail : *Status :finished *version :2.0 *Created Time :2014年09月24日星期三17时45分50秒 ************************************************************************************** *Note: *当缓冲区中有空闲位置时,允许任何一个生产者把它的产品存入; *当缓冲区中无空闲位置时,试图将产品存入缓冲区的任何生产者必须等待; ************************************************************************************** */ #include"producer_consumer.h" void *producer_thread(void *tid) { int i; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); while(1) { sem_wait(&empty); //信号量减一,即看看生产者是否可以向当前指针指向的位置放产品, //保证生产者之间互斥; srand((int)time(NULL) * (int)tid); sleep(1); sem_wait(&mutex); //信号量减一,看看当前时刻缓冲区是否被占用, //保证生产者和消费者之间互斥; // srand((int)time(NULL) * (int)tid); if(NUM>BUFFER_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<BUFFER_SIZE;i++) { printf("%3d",buffer[i]); } printf("\n"); sem_post(&mutex); //释放缓冲区; sem_post(&full); //告诉消费者有产品可以消费; srand((int)time(NULL) * (int)tid); } return 0; } 源文件名:consumer.c 内容: /* ************************************************************************************** *Project/File :consumer.c *By : *Mail : *Status :finished *version :2.0 *Created Time :2014年09月24日星期三17时45分50秒 ************************************************************************************** *Note: *当缓冲区中尚有未取出的产品时,允许任何一个消费者吧其中的一个产品取出; *当缓冲区中没有未取出的产品时,试图从该环内取出产品的任何消费者必须等待; ************************************************************************************** */ #include"producer_consumer.h" void *consumer_thread(void *tid) { int i; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); while(1) { sem_wait(&full); // 从信号量减去一个"1"; srand((int)time(NULL) * (int)tid); sem_wait(&mutex); //对缓冲区互斥使用; // srand((int)time(NULL) * (int)tid); printf("消费者编号:%d\t 指针:%d\t\n",(int)tid,nextc); buffer[nextc] = 0; nextc =(nextc + 1) % BUFFER_SIZE; //指针后移,指向下一个产品; total_consumer++; sleep(1); printf("缓冲区:"); for(i = 0;i < BUFFER_SIZE;i++) { printf("%3d",buffer[i]); } printf("\n"); sem_post(&mutex); //释放缓冲区; sem_post(&empty); //信号量加"1",告诉生产者可以放产品; srand((int)time(NULL) * (int)tid); } return 0; } 文件名:producer_consumer.h 内容: /* ************************************************************************************** *Project/File :producer_consumer.h *By : *Mail : *Status :finished *version :2.0 *Created Time :2014年09月24日星期三17时45分50秒 ************************************************************************************** *Note: *将所有.c文件所用到的头文件包含进去; *包含所有的宏定义; *全局变量的定义; *函数的声明; ************************************************************************************** */ #ifndef producer_consumer_h #define producder_consumer_h #include "stdio.h" #include "stdlib.h" #include "string.h" #include "pthread.h" #include "semaphore.h" #include "signal.h" #include "unistd.h" #define num_producer 5 #define num_consumer 5 #define BUFFER_SIZE 20 int NUM; int total_producer, total_consumer; int buffer[BUFFER_SIZE]; int nextp, nextc; pthread_t threads_p[100], threads_c[100]; sem_t empty, full, mutex; void *producer_thread(void *tid); void *consumer_thread(void *tid); #endif 文件名:Makefile 内容: vpath %.h../producer_consumer producer_consumer:producer.o consumer.o producer_consumer.o gcc -o producer_consumer -pthread producer.o consumer.o producer_consumer.o producer_consumer.o:producer_consumer.c producer_consumer.h gcc -c producer_consumer.c -I./include producer.o:producer.c producer_consumer.h gcc -c producer.c -I./include consumer.o:consumer.c producer_consumer.h gcc -c consumer.c -I./include