随笔 - 12  文章 - 0  评论 - 0  阅读 - 2125

操作系统(7)---进程通信、进程互斥、进程同步与信号量

一、进程通信

进程通信是进程之间的信息交换。主要有三种方式:

  • 管道通信

  e.g ps -aux | grep exp (左边只能写,右边只能读,半双工管道,管道在内核

  管道的实质是一个用于连接读写进程的一个共享文件,固定大小的缓冲区。数据一旦被读出,则从管道中丢弃。没写满不读,没读空不写。

  • 消息传递(可以实现多进程通信
    • 直接通信方式

      消息直接挂到接收进程的消息缓冲队列

      消息=消息头(发送进程ID、接受进程ID、消息类型、消息长度等)+消息体

    • 间接通信方式

      消息先发送至中间实体(信箱)

  • 共享存储(共享空间互斥访问,也叫做临界区,内容称为临界资源)
    • 基于数据结构的共享

      通信时指定数据结构,低级通信方式

    • 基于存储区的共享

      指定内存中的一块存储区域直接进行读写,高级通信方式

二、进程互斥

涉及共享存储的时候,进程互斥可以解决并发进程对临界区的使用问题(临界区资源竞争)。当某一进程在访问临界区的时候,其他进程不可读出或者修改该存储区域的内容。

                                                                 

一般通过锁机制来实现。通过设置标识来表明临界区是否可用。可以实现多个进程互斥访问

                                         

 

三、进程同步与信号量

同步是互相合作的并发进程在一些关键点上互相等待与互通消息,从而实现协调进行。实质是各合作进程的行为保持某种一致性和不变关系。解决了异步环境下进程合作协调推进的问题。

以司机和售票员为例,司机启动车辆需要等待关门信号,售票员开门需要等待司机停车信号。在这种情况下,锁机制失效,因为司机和售票员两个进程不存在资源竞争两次的信号信息不同

 

引入信号量进制来实现同步。信号用来实现P操作(sleep)和V操作(wakeup)

用信号量解决司机售票员问题来进一步理解:

有两个信号量s1、s2。s1代表关门,value初值为0,queue指向司机进程;s2代表停车,value初值为0,queue指向售票员。

司机进程的步骤为:P(s1)——>运行——>V(s2)。先判断是否受到关门信号,来决定发车(继续运行)还是等待关门信号(司机进程sleep)。运行结束,给出停车信号唤醒售票员进程。

售票员进程的步骤为:V(s1)——>坐车——>P(s2)。先给出关门信号唤醒司机进程发车。坐车结束,先判断是否受到停车信号,来决定开门(继续运行)还是等待停车信号(售票员进程sleep)。

整个步骤为:1.司机进程准备运行,P(s1)操作使s1.value--为-1,司机进程休眠执行售票员进程。

      2.售票员进程开始执行,V(s1)操作使s1.value++为0,唤醒司机进程,两个进程并发执行。

      3.售票员进程准备门,P(s2)操作使s2.value--为-1,售票员进程休眠。

      4.司机进程中的V(s2)操作使s2.value++为0,唤醒售票员进程,司机进程结束。

 

信号量也可解决进程互斥问题:

 

上述例子中,一共有两台打印机,一次只能一个进程使用一台打印机:

若设置sem.value的初值为1,queue指向这个进程队列,那么进程1中sem.value--为0不会休眠,可以使用打印机,进程2开始value<0,从而进程234都会休眠必须等待进程1使用完打印机,通过V操作唤醒,两个打印机一次只能用一个,没有充分利用。

若设置sem.value的初值为2,queue指向这个进程队列,那么进程1中sem.value--为1不会休眠,可以使用1号打印机,进程2中sem.value--为0不会休眠,可以使用2号打印机,进程34都会休眠等待进程12使用完打印机,通过V操作唤醒,两个打印机都能充分利用,提高了效率。

 

信号量解决进程同步和进程互斥的区别

初值不同,同步的信号量初值一般设为0;互斥的信号量初值设为非0正数,和临界资源个数有关,若为负数,则代表休眠进程的个数或者说休眠队列的长度。 

 

Linux信号量实现:

有名信号量:一个特殊文件,存于/dev/shm路径下,可以被任何知道其名字的进程使用,适用于多进程合作,进程退出后需要删除并释放资源。

常用函数:1.sem_t  *sem_open ( const char *name , int oflag , mode_t mode , unsigned int value ) ; 打开信号量文件,以name命名,设初值为value。

        2.int  sem_wait ( sem_t *sem ) ; P操作

        3.int  sem_post ( sem_t *sem ) ; V操作

        4.int  sem_close ( sem_t *sem ) ; 关闭文件

        5.int  sem_unlink ( const char *name ) ; 删除,45常合用,先关闭文件后删除

无名信号量:只存于内存中,适用于一个进程的多线程合作,用完也要删除并释放资源。

 

常用函数:1.int  sem_init ( sem_t *sem , int pshared , unsigned int value ) ; 设置无名信号量

 

        2.int  sem_wait ( sem_t *sem ) ; P操作

 

        3.int  sem_post ( sem_t *sem ) ; V操作

 

        4.int  sem_destroy ( sem_t *sem ) ; 删除

 

实例(有名信号量在多进程中的应用):

复制代码
#include<stdio.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/wait.h>

int main(int argc,char **argv)
{
        int pid;
        sem_t *sem;
        const char sem_name[]="mysqm";

        pid=fork();

        if(pid<0){
                printf("Error in the fork\n");
        }
        else if(pid==0){
                sem=sem_open(sem_name,O_CREAT,0644,1);
                if(sem==SEM_FAILED){
                        printf("unable to create semaphore\n");
                        sem_unlink(sem_name);
                        exit(-1);
                }
                sem_wait(sem);
                for(int i=0;i<3;++i){

                        printf("child process run:%d\n",i);
                        sleep(1);
                }
                sem_post(sem);
        }
        else{
                sem=sem_open(sem_name,O_CREAT,0644,1);
                if(sem==SEM_FAILED){
                        printf("unable to create semaphore\n");
                        sem_unlink(sem_name);
                        exit(-1);}
                sem_wait(sem);
                for(int i=0;i<3;++i)
                {
                        printf("parent process run: %d\n",i);
                        sleep(1);
                }
                sem_post(sem);
                wait(NULL);
                sem_close(sem);
                sem_unlink(sem_name);
        }
        return 0;
}
复制代码

 

结果:

 

 

 

 

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

问题描述:若仓库已满,生产者不能再生产,必须等待消费;若仓库是空的,消费者不能再消费,必须等待生产;若库存充足,生产者和消费者不能同时对仓库库存进行修改。

问题分析:该问题同时包含了进程互斥和进程同步,需要三个信号量s1、s2、s3,s1代表仓库是否已满,s2代表仓库是否已空,s3代表仓库库存是否正在被修改。进程互斥的value初值取决于临界资源的个数,这里只有一个仓库,故s3.value设置是为1。

生产者的步骤为:p(s1)判断是否可以生产——>p(s3)占用临界区——>生产——>v(s3)释放临界区——>v(s2)提醒消费者可以消费了。

消费者的步骤为:p(s2)判断是否可以消费——>p(s3)占用临界区——>消费——>v(s3)释放临界区——>v(s2)提醒生产者有仓库有空位可以生产了。

关于s1和s2初值value设定:按照上文所说,若都设置为0,生产者和消费者一起休眠,且s1s2同时为0,表示仓库又满又空,这是矛盾的。所以,s1和s2的初值不能同时为0,s1.value代表仓库还有多少空位,s2.value代表仓库还有多少库存。

注意:同时有进程同步和进程互斥时,要先同步后互斥,先确定是否有继续执行的条件再给予临界区控制权,否则会出现僵持(例如满仓时,若生产者先执行,占用了临界区,p(s1)操作使生产者休眠,转而执行消费者,然而生产者未释放临界区资源,消费者也不可用,两个进程出现僵持)。

问题模拟代码:

复制代码
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/ipc.h>

#define NBUFF 10

int nitems=2;

struct {
        int count;
        sem_t *mutex,*empty,*full;
}shared;

void *produce(void *arg){
        int i;
        for(i=0;i<nitems;i++){
                sem_wait(shared.full);
                sem_wait(shared.mutex);
                shared.count++;
                printf("produce successfully! now:%d\n",shared.count);
                sleep(1);
                sem_post(shared.mutex);
                sem_post(shared.empty);
        }
        return NULL;
}

void *consume(void *arg){ int i; for(i=0;i<nitems;i++){ sem_wait(shared.empty); sem_wait(shared.mutex); shared.count--; printf("consume successfully! now:%d\n",shared.count); sleep(1); sem_post(shared.mutex); sem_post(shared.full); } return NULL; } int main(int argc,char **argv){ pthread_t tp,tc; const char s1[]="full"; const char s2[]="empty"; const char s3[]="mutex"; shared.full=sem_open(s1,O_CREAT,0644,NBUFF); shared.empty=sem_open(s2,O_CREAT,0644,0); shared.mutex=sem_open(s3,O_CREAT,0644,1); pthread_create(&tp,NULL,produce,NULL); pthread_create(&tc,NULL,consume,NULL); pthread_join(tp,NULL); pthread_join(tc,NULL); sem_unlink(s1); sem_unlink(s2); sem_unlink(s3); return 0; }
复制代码

 

结果:

 

posted on   小光翎  阅读(537)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示