您好,欢迎来到飒榕旅游知识分享网。
搜索
您的当前位置:首页实验04、进程同步问题

实验04、进程同步问题

来源:飒榕旅游知识分享网
班级:17软件3班 学号:201758234156 姓名:曾强

实验04

1.实验目的

进程同步问题

(1)熟悉临界资源、信号量及PV操作的定义与物理意义 (2)掌握进程互斥与进程同步的相关知识

(3)掌握用信号量机制解决进程之间的同步与互斥问题 (4)实现生产者-消费者问题,深刻理解进程同步问题 2.实验类型:设计型 3.实验学时:2 4.实验原理和知识点

(1) 实验原理:用信号量机制实现进程的同步与互斥 (2) 知识点:信号量

5.实验环境(硬件环境、软件环境):

(1)硬件环境:Intel Pentium III 以上CPU,128MB以上内存,2GB以上硬盘 (2)软件环境:linux操作系统。 6. 预备知识

(1)信号

signal.h是C标准函数库中的信号处理部分,定义了程序执行时如何处理不同的信号。信号用作进程间通信,报告异常行为(如除零)、用户的一些按键组合(如同时按下Ctrl与C键,产生信号SIGINT)。

信号是一种软件中断,用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。

在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,

1

进程并不知道在处理之前来过多少个。

每个信号都有一个名字,这些名字都以3个字符SIG开头。编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。表2.2是编号为1~20的信号。

表2.2 信号

信号名 SIGHUP SIGINT SIGQUIT 信号值 默认动作 1 2 3 终止进程 终止进程 终止进程 说明 通知同一会话内的各个作业, 这时它们与控制终端不再关联。 当用户按中断键(Ctrl-C)时,终端驱动程序产生此信号,信号送到前台进程组的每个进程。 当用户按退出键(Ctrl-\\)时,终端驱动程序产生此信号,信号送到前台进程组的每个进程。 执行了非法指令。通常是因为可执行文件本身出现错误, 或者试图执行数据段。堆栈溢出时也有可能产生这个信号。 由断点指令或其它trap指令产生。由debugger使用。 调用abort函数生成的信号。 非法地址,包括内存地址对齐出错。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。 在发生致命的算术运算错误时发出。不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。 用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。 用户定义信号1 (留给用户使用) 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。 用户定义信号2 (留给用户使用) 管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。 时钟定时信号, 计算的是实际的时间或时钟时间。 alarm函数使用该信号。 程序结束信号,该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,用户才会尝试2

SIGILL SIGTRAP SIGABRT 4 5 6 建立CORE文件 建立CORE文件 建立CORE文件 SIGBUS 7 建立CORE文件 SIGFPE 8 建立CORE文件 SIGKILL SIGUSR1 SIGSEGV SIGUSR2 9 终止进程 10,16 终止进程 11 终止进程 12,17 终止进程 SIGPIPE 13 终止进程 SIGALRM 14 终止进程 SIGTERM 15 终止进程

信号名 SIGCHLD SIGCONT SIGSTOP SIGTSTP 信号值 默认动作 17 18 19 20 忽略信号 忽略信号 停止进程 停止进程 说明 SIGKILL。 子进程结束时, 父进程会收到这个信号。 让一个停止的进程继续执行。本信号不能被阻塞。 暂时停止进程的执行。本信号不能被阻塞, 处理或忽略。 停止进程的运行, 但该信号可以被处理和忽略。用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号。 要查询系统中的信号,可以使用命令:kill –l

要了解信号的意义及其用法,可以使用命令:man 7 signal signal()系统调用

头文件:#include

函数原型:void(*signal(int signumber,void(*func)(int)))(int); 函数功能:signal的功能是捕捉信号,给信号指定处理函数。 函数参数:

signumber是要捕捉的信号;

func()是指定给signumber的处理函数。func()可以是下面3种类型: (1) 用户定义函数。

(2) SIG_IGN,告诉系统忽略这个信号。

(3) SIG_DFL,告诉系统对这个信号的处理采用系统默认的处理。 如:signal(SIGALRM,handler); alarm()系统调用

所需头文件:#include

函数原型:unsigned int alarm(unsigned int seconds); seconds:指定秒数

功能与作用:alarm()函数的主要功能是设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALARM的处理函数,那么alarm()默认处理终止进程。

函数返回值:如果在seconds秒内再次调用了alarm函数设置了新的闹钟,则后面定时器的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代;当参数seconds为0时,之前设置的定时器闹钟将被取消,并将剩下的时间返回。

(2)线程Pthread

POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为

3

POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准。POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。

头文件:#include 数据类型

pthread_t:线程ID pthread_attr_t:线程属性 操纵函数

pthread_create():创建一个线程 pthread_exit():终止当前线程

pthread_cancel():中断另外一个线程的运行

pthread_join():阻塞当前的线程,直到另外一个线程运行结束 pthread_setcancelstate:设置当前线程的“可取消性”状态。 (3)POSIX 信号量

http://blog.csdn.net/gatieme/article/details/50994533 头文件:#include 数据类型

sem_t:信号量的数据类型 操纵函数

sem_init:信号量设初值 sem_destroy:销毁信号量 sem_wait:P操作 sem_post:V操作 srand和rand函数 头文件:#include/ srand(设置随机数种子) 头文件:#include

函数原型:void srand (unsigned int seed); 函数说明:

4

srand()用来设置rand()产生随机数时的随机数种子。参数seed必须是个整数,通常可以利用geypid()或time(0)的返回值来当做seed。如果每次seed都设相同值,rand()所产生的随机数值每次就会一样。

exit()系统调用

头文件:#include 函数原型:void exit(int status);

函数功能:exit的功能是正常终止进程自己。 函数参:status是返回给父进程结束状态码。 pause()系统调用

头文件 #include 函数原型 int pause(void);

函数功能:pasuse的功能是将进程挂起直到有一个信号被接收处理。就是说,只有当一个信号处理函数被执行且返回之后pause才返回。

7.实验内容及步骤: (1)任务描述:

用信号量实现经典的线程间生产者-消费者问题(Producer-Consumer Problem)。

(2)程序设计过程:

在本问题中,共需要一个3个信号量:Mutex和consumer_semaphore、producer_semaphore。其中,Mutex是用来锁定临界区的,以解决对共享数据buffer的互斥访问问题(无论是对生成者还是对消费者); 生产者会在缓冲区为满时被阻塞,所以\"非满\"也是一种稀缺资源.需要设置一个信号量producer_semaphore,初值设为buffer的大小MAX_BUFFER;消费者会在缓冲区为空时被阻塞,所以\"非空\"是一种稀缺资源;需要设置一个信号量consumer_semaphore,初值设为0;

设共享数据:

Semaphore buffer_mutex=1;

Semaphore producer_semaphore=MAX_BUFFER; Semaphore consumer_semaphore=0; int buffer[MAX_BUFFER];

5

Producer线程的处理函数: while(1){

Wait(producer_semaphore); Wait(buffer_mutex); Buffer[pn]=product; pn=(pn+1)%MAX_BUFFER; Signal(consumer_semaphore); Signal(buffer_mutex); Sleep(); }

consumer线程的处理函数: while(1){

Wait(consumer_semaphore); Wait(buffer_mutex); Consume=buffer[cn]; cn=(cn+1)%MAX_BUFFER; Signal(producer_semaphore); Signal(buffer_mutex); Sleep(); }

(3)程序实现(C语言) #include #include #include #include #include #include #include #include #include #include

6

//生产者的个数

#define NUM_THREADS_P 5 //消费者的个数

#define NUM_THREADS_C 5 //缓冲区的大小

#define MAX_BUFFER 20 //运行的时间

#define RUN_TIME 20 //缓冲区的数组

int buffer[MAX_BUFFER];

int produce_pointer=0,consume_pointer=0;

//C语言中,信号量的数据类型为结构sem_t,它本质上是一个长整型的数。 sem_t producer_semaphore,consumer_semaphore,buffer_mutex;

pthread_t threads_p[NUM_THREADS_P]; /*变量声明,producer线程id*/ pthread_t threads_c[NUM_THREADS_C]; /*变量声明,consumer线程id*/ FILE* fd;

void *producer_thread(void *tid); void *consumer_thread(void *tid); void showbuf(); void handler(){

int i;

for(i=0;i//送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread }

int main(){

//会终止。

pthread_cancel(threads_p[i]); pthread_cancel(threads_c[i]);

for(i=0;iint i;

7

signal(SIGALRM,handler); /*signal(SIGALRM, handler); 表示给当前进程注册

SIGALRM信号处理代码,如果收到SIGALRM信号,就会去执行handler函数*/

fd=fopen(\"output.txt\ /*open a file to save the result*/ sem_init(&producer_semaphore,0,MAX_BUFFER); /*set the value of

semaphores中间的0表示为进程内的线程所共享,非0表示为进程间共享*/

/*create the threads*/

for(i=0;ipthread_create(&threads_p[i],NULL,(void*)producer_thread,(void*)(i+1)); sem_init(&consumer_semaphore,0,0); sem_init(&buffer_mutex,0,1); for(i=0;ibuffer[i]=0; /*initiate the buffer缓冲区全部初始化为0*/

/*创建线程,由threads_p[i]指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性,此处为NULL。新创建的线程从

producer_thread函数的地址开始运行,该函数只有一个万能指针参数arg(此处为i+1),如果需要向producer_thread函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。*/

for(i=0;ipthread_create(&threads_c[i],NULL,(void*)consumer_thread,(void *)(i+1)); alarm(RUN_TIME); /*set time to run*/ /*wait the threads to exit*/ for(i=0;ipthread_join(threads_p[i],NULL); pthread_join(threads_c[i],NULL); for(i=0;isem_destroy(&producer_semaphore); sem_destroy(&consumer_semaphore); sem_destroy(&buffer_mutex); fclose(fd);

8

}

return 0;

void *producer_thread(void *tid){

sem_wait(&buffer_mutex);

buffer[produce_pointer]=rand()%20+1;/*生产一个1~20之间的随机数存入/*the thread can be canceled by other thread*/

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); while(1){

sem_wait(&producer_semaphore); srand((int)time(NULL)*(int)tid);

sleep(rand()%2+1); /*随机阻塞1或2秒one or two seconds to produce*/ //while((produce_pointer+1)%20==consume_pointer);

缓冲区*/

produce_pointer=(produce_pointer+1)%20;/*生产者指针后移*/ if(produce_pointer==0){ /*if buffer was filled to the 19th*/

printf(\"producer:%d pointer_p:%2d fprintf(fd,\"producer:%d pointer_p:%2d

produced:%2d\\n\produced:%2d\\n\

/*生产者编号1-5,指针编号0-19,缓冲区编号0-19*/

} else{ } showbuf();

sem_post(&buffer_mutex);

sem_post(&consumer_semaphore); /*inform the consumer the buffer is not

9

printf(\"producer:%d pointer_p:%2d fprintf(fd,\"producer:%d pointer_p:%2d

produced:%2d\\n\produced:%2d\\n\

empty*/ }

void *consumer_thread(void *tid){

/*the thread can be canceled by other thread*/

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); while(1){

}

return ((void*)0);

srand((int)time(NULL)*(int)tid);

sleep(rand()%5+1); /*wait a few seconds ,then continue producing*/

//信号等待

sem_wait(&consumer_semaphore);

//获取种子

srand((int)time(NULL)*(int)tid);

//休眠一到两秒

sem_wait(&buffer_mutex);

printf(\"consumer:%d pointer_c:%2d fprintf(fd,\"consumer:%d pointer_c:%2d buffer[consume_pointer]=0;

consume_pointer=(consume_pointer+1)%20; showbuf();

sem_post(&buffer_mutex); sem_post(&producer_semaphore); srand((int)time(NULL)*(int)tid);

sleep(rand()%5+1); /*wait a few seconds (1-5),then continue

sleep(rand()%2+1); /*one or two seconds to consume*/

consumed:%2d\\n\consumed:%2d\\n\

consuming*/

}

return ((void*)0);

10

}

/*show the content of buffer*/ void showbuf(){ }

(4)编译运行上述程序,观察实验结果,结合实验结果进一步理解实验程序。注意,由于pthread不是标准的C/C++库函数,在在链接阶段,需要使用库libpthread.a。所以,编译命令要加上参数-lpthread ,即为: gcc -o e301 e301.c -lpthread

int i;

printf(\"buffer:\"); fprintf(fd,\"buffer:\");

for(i=0;iprintf(\"\\n\\n\"); fprintf(fd,\"\\n\\n\");

printf(\"%2d \fprintf(fd,\"%2d \

11

(5)参考上述实验代码,结合下面的伪代码,设计实现下述生产者-消费者问题。

桌上有一空盘,允许存放一只水果。爸爸可向盘中存放苹果,也可向盘中存放桔子,儿子专等吃盘中的桔子,女儿专等吃盘中的苹果。规定当盘空时一次只能放一只水果供吃者取用,请用P、V原语实现爸爸、儿子、女儿三个并发进程的同步。 Semaphore Sa,So=0; Semaphore Sp=1;

father线程的处理函数: while(1){ Wait(Sp);

printf(\"父亲随机放苹果或橘子\"); srand((int)time(NULL)); result= rand()%2+1; if(result==1)

{printf(\"父亲放的是橘子\\n \"); signal (So);}

12

else

{printf(\"父亲放的是苹果\\n \"); signal(Sa);} Sleep(); }

son线程的处理函数: while(1){ wait(So);

printf(\"儿子从盘中取出桔子\\n \"); signal(Sp);

printf(\"儿子吃桔子\\n \"); Sleep(); }

daughter线程的处理函数: while(1){ wait(Sa);

printf(\"女儿从盘中取出苹果子\\n \"); signal(Sp);

printf(\"女儿吃苹果\\n \"); Sleep(); }

请在下方贴出源代码:

13

14

请在下方贴出运行截图:

实验心得体会:

进程的同步问题的实现主要通过对互斥变量的控制,PV操作来实现不同进程的执行。

在虚拟机中编译时它不允许main函数使用for循环来控制父亲放水果的次数,会报错,但是用while就没问题。

信号量的初始化时,sem_init()中第三个参数是干什么的不了解,是赋值吗?

15

教师评语及成绩

16

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- sarr.cn 版权所有 赣ICP备2024042794号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务