李治军老师操作系统实验5---信号量的实现和应用-信号量可以是-种资源的数量-进程看信号量实现同步即走走停停-信号临界区保护:共享数据的更新过程不能中断-此时切换去执行其他进程,切后进程看的是错信号量

改自原文链接:https://blog.csdn.net/weixin_43166958/article/details/104163221
一、重要资料资料
满时不生产,空时不消耗:
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
消费者-------消耗资源中数据-------等数据
生产者-------产生数据存入资源----等资源
1️⃣信号量等于0--------------资源中无数据,无消费者无生产者
2️⃣信号量大于0--------------资源中有数据,消费者与生产者可能在流水作业,也希望是流水作业,拥堵肯定是不好的。拥塞是有生产者睡着等资源存储数据
3️⃣信号量小于0--------------资源中无数据,有消费者在睡着等数据

二、本次实验的基本内容是:
在 Ubuntu 下编写程序,用信号量解决生产者——消费者问题;
在 0.11 中实现信号量,用生产者—消费者程序检验之。

1
用信号量解决生产者—消费者问题

实验要求:pc.c程序需打开一个文件buffer.txt作为共享缓冲区,缓冲区同时最多只能保存 10 个数;创建一个生产者进程和N个消费者进程,其中生产者进程向缓冲区写入连续的整数,0,1,2,……,M,M>=500;消费者进程从缓冲区依次读取数字,每次读一个,并将读出的数字从缓冲区删除,然后将本进程 ID 和数字输出到标准输出。

(1)
信号量相关函数-------根据这些函数编写生产者消费者模型代码 pc.c
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);

sem_open() 的功能是创建一个信号量,或打开一个已经存在的信号量。如失败,返回值是 NULL
name 是信号量的名字。不同的进程可以通过提供同样的 name 而共享同一个信号量。
value 是信号量的初值,仅当新建信号量时,此参数才有效,其余情况下它被忽略。

int sem_wait(sem_t *sem);

sem_wait() 就是信号量的 P (生产者)原子操作(硬件原子操作指令与进出栈,中断指令是一样的)。返回 0 表示成功,返回 -1 表示失败。
如果信号量的值比0大,那么进行减一的操作,函数立即返回,往下执行。如果信号量当前为0值,那么调用就会一直阻塞或者直到信号量变得可以进行减一操作,达到阻塞进程的目的.

int sem_post(sem_t *sem);

sem_post() 就是信号量的 V (消费者)原子操作。如果有等待 sem 的进程,它会唤醒其中的一个。返回 0 表示成功,返回 -1 表示失败。
sem_post函数的作用是给信号量的值加上一个“1”,即解锁操作; 它是一个原子操作。

int sem_unlink(const char *name);

sem_unlink() 的功能是删除名为 name 的信号量。返回 0 表示成功,返回 -1 表示失败。

off_t lseek(int filedes, off_t offset, int whence);

lseek() 函数可以改变文件的文件偏移量,所有打开的文件都有一个当前文件偏移量,用于表明文件开始处到文件当前位置的字节数。成功返回新的偏移量,失败返回-1。
SEEK_SET 将读写位置指向文件头后再增加offset个位移量。
SEEK_CUR 以目前的读写位置往后增加offset个位移量。
SEEK_END 将读写位置指向文件尾后再增加offset个位移量。
用lseek创建一个空洞文件
空洞文件就是这个文件有一段是空的;
普通文件中间不能有空,write文件时从前往后移动文件指针,依次写入;
用lseek往后跳过一段,就形成空洞文件;
空洞文件对多线程共同操作文件非常有用。需要创建一个很大文件时,从头开始依次创建时间很长,可以将文件分成多段,多个线程操作每个线程负责其中一段的写入。
ret = lseek(fd, 10, SEEK_SET);

(2)
信号量实现生产者消费者模型代码 pc.c:

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>          
#include <sys/stat.h>        
#include <semaphore.h>
#include <sys/types.h>  
#include <sys/wait.h>

#define M 530    		/*打出数字总数*/
#define N 5       		/*消费者进程数*/
#define BUFSIZE 10      /*缓冲区大小*/

int main()
{
	sem_t *empty, *full, *mutex;/*3个信号量*/
	int fd; /*共享缓冲区文件描述符*/
    int  i,j,k,child;
    int  data;/*写入的数据*/
    pid_t pid;
    int  buf_out = 0;  /*从缓冲区读取位置*/
    int  buf_in = 0;   /*写入缓冲区位置*/
	/*打开信号量*/
	empty = sem_open("empty", O_CREAT|O_EXCL, 0644, BUFSIZE); /*剩余资源,初始化为size*/
    full = sem_open("full", O_CREAT|O_EXCL, 0644, 0);         /*已使用资源,初始化为0*/
	mutex = sem_open("mutex", O_CREAT|O_EXCL, 0644, 1);       /*互斥量,初始化为1*/
    
    fd = open("buffer.txt", O_CREAT|O_TRUNC|O_RDWR,0666); 
    lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);/*刷新了40个字节的缓冲区,存放10个数字*/
    write(fd,(char *)&buf_out,sizeof(int));/*将待读取位置存入buffer后,以便子进程之间通信*/
    /*生产者进程*/
    if((pid=fork())==0)
    {
		printf("I'm producer. pid = %d\n", getpid());
		/*生产多少个产品就循环几次*/
        for( i = 0 ; i < M; i++)
        {
			/*empty大于0,才能生产*/
            sem_wait(empty);
            sem_wait(mutex);
            /*写入一个字符*/
            lseek(fd, buf_in*sizeof(int), SEEK_SET); 
            write(fd,(char *)&i,sizeof(int)); 
            /*更新写入缓冲区位置,保证在0-9之间*/
			/*生产完一轮产品(文件缓冲区只能容纳BUFSIZE个产品编号)后*/
			/*将缓冲文件的位置指针重新定位到文件首部。*/			
            buf_in = (buf_in + 1) % BUFSIZE;
            sem_post(mutex);
            sem_post(full);/*共享区中已使用资源++,唤醒消费者线程*/
        }
        printf("producer end.\n");
        fflush(stdout);/*确保将输出立刻输出到标准输出。*/
        return 0;
    }
	else if(pid < 0)
    {
        perror("Fail to fork!\n");
        return -1;
    }
	/*消费者进程*/
    for( j = 0; j < N ; j++ )
    {
        if((pid=fork())==0)
        {
			for( k = 0; k < M/N; k++ )
            {
                sem_wait(full);/*共享区中已使用资源--,一开始为0会阻塞此处*/
                sem_wait(mutex);
				
                /*获得读取位置*/
                lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);
                read(fd,(char *)&buf_out,sizeof(int));
                /*读取数据*/
                lseek(fd,buf_out*sizeof(int),SEEK_SET);
                read(fd,(char *)&data,sizeof(int));
                /*写入读取位置*/
                buf_out = (buf_out + 1) % BUFSIZE;
                lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);
                write(fd,(char *)&buf_out,sizeof(int));

                sem_post(mutex);
                sem_post(empty);/*共享区中剩余资源++,唤醒生产者进程*/
                /*消费资源*/
                printf("%d:  %d\n",getpid(),data);
                fflush(stdout);
            }
			printf("child-%d: pid = %d end.\n", j, getpid());
            return 0;
        }
		else if(pid<0)
		{
			perror("Fail to fork!\n");
			return -1;
		}
	}
	/*回收线程资源*/
    child = N + 1;
    while(child--)
        wait(NULL);
    /*释放信号量*/
    sem_unlink("full");
    sem_unlink("empty");
    sem_unlink("mutex");
    /*释放资源*/
    close(fd);
    return 0;
}

(3)
在ubuntu下执行,结果如下:

gcc pc.c -o pc -lpthread
./pc > 1.txt
cat 1.txt  | more

李治军老师操作系统实验5---信号量的实现和应用-信号量可以是-种资源的数量-进程看信号量实现同步即走走停停-信号临界区保护:共享数据的更新过程不能中断-此时切换去执行其他进程,切后进程看的是错信号量
李治军老师操作系统实验5---信号量的实现和应用-信号量可以是-种资源的数量-进程看信号量实现同步即走走停停-信号临界区保护:共享数据的更新过程不能中断-此时切换去执行其他进程,切后进程看的是错信号量

2
在 linux-0.11 中实现信号量

上一篇:Linux进程间通信之信号量


下一篇:vbs脚本和windows定时任务实现qq消息表情包定时发送