• 【Linux-Day10-信号量,共享内存,消息队列】


    信号量

    信号量描述

    信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源 时,需要对信号量的值进行原子减一,该操作被称为 P 操作。

    当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。

    释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。

    信号量主要用来同步进程。

    信号量的值如果只取 0,1,将其称为二值信号量。

    如果信 号量的值大于 1,则称之为计数信号量。

    **临界资源:同一时刻,只允许被一个进程或线程访问的资源 **

    **临界区:访问临界资源的代码段 **

    信号量使用

    semget(); 创建或者获取已存在的信号量

    int semget(key_t key, int nsems, int semflg);

    semget()成功返回信号量的 ID, 失败返回-1

    key:两个进程使用相同的 key 值,就可以使用同一个信号量

    nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号 量的个数

    semflg 可选: IPC_CREAT IPC_EXCL

    semop()对信号量进行改变,做 P 操作或者 V 操作

    int semop(int semid, struct sembuf *sops, unsigned nsops);

    semop()成功返回 0,失败返回-1

    struct sembuf

    {

    unsigned short sem_num; //指定信号量集中的信号量下标

    short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作

    short sem_flg; //SEM_UNDO

    };

    semctl()控制信号量

    int semctl( int semid, int semnum, int cmd, …);
    semctl()成功返回 0,失败返回-1

    semid:信号量的ID

    cmd 选项: SETVAL IPC_RMID

    union semun
    {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
    };

    封装一个c文件实现创建一个信号

    #include 
    #include 
    #include 
    #include 
    #include 
    
    union semun
    {
        int val;
    };
    static int semid = -1;
    void sem_init()
    {
        semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建信号量,如果存在就失败
        if ( semid == -1 )//失败,表示该(key_t)1234)信号已存在
        {
            semid = semget((key_t)1234,1,0600);//获取已存在的信号量id
            if ( semid == -1)
            {
                printf("semget err\n");
            }
        }
        else//全新创建成功,那么要进行初始化
        {
            union semun a;
            a.val = 1;//信号量的初始值
            if ( semctl(semid,0,SETVAL,a) == -1)//设置初始值
            {
                printf("semctl err\n");
            }
        }
    }
    void sem_p()
    {
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = -1; //p操作
        buf.sem_flg = SEM_UNDO;
    
        if ( semop(semid,&buf,1) == -1)
        {
            printf("semop p err\n");
        }
    
    }
    void sem_v()
    {
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = 1; //v操作
        buf.sem_flg = SEM_UNDO;
    
        if ( semop(semid,&buf,1) == -1)
        {
            printf("semop v err\n");
        }
    }
    void sem_destroy()
    {
        if ( semctl(semid,0,IPC_RMID) == -1)
        {
            printf("semctl destroy err\n");
        }
    
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    假设资源只有一份,每轮a进程使用2次 ,b进程使用3次,如何解决。

    我们可以使用信号量解决临界资源问题

    a进程代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "sem.c"
    int main()
    {
        sem_init();//
        for(int i = 0; i < 5; i++)
        {
            //p
             sem_p();
            printf("a");
            fflush(stdout);
            int n = rand() % 3;
            sleep(n);
            printf("a");
            fflush(stdout);
            sem_v();
    
            n = rand() % 3;
            sleep(n);
        }
    
        sleep(10);
        sem_destroy();
        return 0;
    }
    
    • 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

    b进程代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "sem.c"
    int main()
    {
        sem_init();
        for(int i = 0; i < 5; i++)
        {
            sem_p();
            printf("b"); 
            int n = rand() % 3;
            sleep(n);
            printf("bb");
            fflush(stdout);
            sem_v();
    
            n = rand() % 3;
            sleep(n);
        }
        return 0;
    }
    
    • 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

    效果如下:a,b不会同时访问该资源

    共享内存

    共享内存原理

    共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理 内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访 问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供 同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。

    shemget()创建共享内存

    int shmget(key_t key, size_t size, int shmflg);
    shmget()用于创建或者获取共享内存
    shmget()成功返回共享内存的 ID, 失败返回-1
    key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
    size: 创建共享内存时,指定要申请的共享内存空间大小
    shmflg: IPC_CREAT IPC_EXCL

    shmat() 用来创建映射

    void * shmat( int shmid, const void *shmaddr, int shmflg);
    shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
    shmat()成功返回返回共享内存的首地址,失败返回 NULL
    shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
    shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写

    shmdt()用来断开映射

    int shmdt( const void *shmaddr);
    shmdt()断开当前进程的 shmaddr 指向的共享内存映射
    shmdt()成功返回 0, 失败返回-1

    shmctl()用来控制共享内存

    int shmctl( int shmid, int cmd, struct shmid_ds *buf);

    shmctl()成功返回 0,失败返回-1

    cmd: IPC_RMID 32. *

    测试代码:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        int shmid = shmget((key_t)1234,sizeof(char)*128,IPC_CREAT|0600);
        if(shmid ==  -1)
        {
            printf("shmget error\n");
            exit(1);
        }
        char* p = shmat(shmid,NULL,SHM_W);
        if(p == NULL)
        {
            printf("shmat error\n");
            exit(2);
        }
        while(1)
        {
            char buff[128]={0};
            printf("parent input: ");
            fflush(stdout);
            fgets(buff,127,stdin);
            if(strncmp(buff,"end",3) == 0)
            {
                break;
            }
            int pid = fork();
           if(pid == -1) break;
           if(pid != 0){ 
               strcpy(p,buff);
           }
           if(pid == 0){
               char* ptr=shmat(shmid,NULL,0);
               printf("child read: %s\n",ptr);
               shmdt(ptr);
               exit(0);
           }
        wait(NULL);
        }
        shmdt(p);
        exit(0);
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    结果如图:

    在这里插入图片描述

    下面我们用信号量来实现对共享内存的访问。

    代码如下:

    sem.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define SEM1 0
    #define SEM2 1
    union semun
    {
        int val;
    };
    static int semid = -1;
    
    void sem_init()
    {
        semid = semget((key_t)1234, 2, IPC_CREAT | IPC_EXCL | 0600); // 全新创建信号量,如果存在就失败
        if (semid == -1)                                             // 失败,表示已存在
        {
            semid = semget((key_t)1234, 2, 0600); // 获取已存在的信号量id
            if (semid == -1)
            {
                printf("semget err\n");
            }
        }
        else // 全新创建成功,那么要进行初始化
        {
            union semun a;
            const int ar[2] = {1, 0};
            for (int i = 0; i < 2; ++i)
            {
                a.val = i;                             // 信号量的初始值
                if (semctl(semid, i, SETVAL, a) == -1) // 设置初始值
                {
                    printf("semctl err\n");
                }
            }
        }
    }
    void sem_p(int sem)
    {
        struct sembuf buf;
        buf.sem_num = sem;
        buf.sem_op = -1; // p
        buf.sem_flg = SEM_UNDO;
    
        if (semop(semid, &buf, 1) == -1)
        {
            printf("semop p err\n");
        }
    }
    void sem_v(int sem)
    {
        struct sembuf buf;
        buf.sem_num = sem;
        buf.sem_op = 1; // v
        buf.sem_flg = SEM_UNDO;
    
        if (semop(semid, &buf, 1) == -1)
        {
            printf("semop v err\n");
        }
    }
    void sem_destroy()
    {
        if (semctl(semid, 0, IPC_RMID) == -1)
        {
            printf("semctl destroy err\n");
        }
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    read.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "sem.c"
    
    int main()
    {
        int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
        if ( shmid == -1 )
        {
            printf("shmget err\n");
            exit(1);
        }
    
        char * s = (char*)shmat(shmid,NULL,0);
        if ( s == (char*)-1)
        {
            printf("shmat err\n");
            exit(1);
        }
    
        sem_init();
         while( 1 )
        {
            sem_p(SEM2);
            if ( strncmp(s,"end",3) == 0 )
            {
                break;
            }
    
            printf("read:%s\n",s);
            sem_v(SEM1);
        } 
    
        shmdt(s);
        shmctl(shmid,IPC_RMID,NULL);
        sem_destroy();
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    write.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "sem.c"
    
    int main()
    {
        int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
        if ( shmid == -1 )
        {
            printf("shmget err\n");
            exit(1);
        }
    
        char* s = (char*)shmat(shmid,NULL,0);
        if ( s == (char*)-1) 
        {
            printf("shmat err\n");
            exit(1);
        }   
    
        sem_init();
        while( 1 )
        {
            printf("input: ");
            char buff[128] = {0};
            fflush(stdout);
            fgets(buff,128,stdin);
    
            sem_p(SEM1);
            strcpy(s,buff);
            sem_v(SEM2);
    
            if ( strncmp(buff,"end",3) == 0)
            {
                break;
            }
        }
    
        shmdt(s);
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    消息队列

    接口介绍
    1.msgget() 获取消息队列

    int msgget(key_t key, int msqflg);
    msgget()创建或者获取一个消息队列
    msgget()成功返回消息队列 ID,失败返回-1
    msqflg: IPC_CREAT

    2.msgsnd()发送信息

    int msgsnd( int msqid, const void *msqp, size_t msqsz, int msqflg);
    msgsnd()发送一条消息,消息结构为:
    struct msgbuf
    {
    long mtype; // 消息类型, 必须大于 0 必须有
    char mtext[1]; // 消息数据
    };
    msgsnd()成功返回 0, 失败返回-1
    msqsz: 指定 mtext 中有效数据的长度
    msqflg:一般设置为 0 可以设置 IPC_NOWAIT

    3.msgrcv()接收消息

    ssize_t msgrcv( int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
    msgrcv()接收一条消息
    msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
    msqtyp: 指定接收的消息类型,类型可以为 0(忽略类型)
    msqflg: 一般设置为 0 可以设置 IPC_NOWAIT

    4.msgctl()控制消息队列

    int msgctl( int msqid, int cmd, struct msqid_ds *buf);
    msgctl()控制消息队列
    msgctl()成功返回 0,失败返回-1
    cmd: IPC_RMID

    测试代码:

    msgread.c //从消息队列中读取
    #include
    #include
    #include
    #include
    #include
    #include
    struct message
    {
        long type;//固定
        char msg[16];
    };
    int main()
    {
        int msgid=msgget((key_t)1234,IPC_CREAT|0600);
        if(msgid==-1)
        {
            printf("msgget err\n");
            exit(1);
        }
        struct message dt;
        msgrcv(msgid,&dt,16,1,0);//0代表不区分类型
        printf("read message:%s\n",dt.msg);
        exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    msgcreat.c //写入数据
    #include
    #include
    #include
    #include
    #include
    #include
    struct message //自定义结构体
    {
        long type;//固定的
        char msg[16];
    };
    int main()
    {
        int msgid=msgget((key_t)1234,IPC_CREAT|0600);
        if(msgid==-1)
        {
            printf("msgget err\n");
            exit(1);
        }
        struct message dt;
        dt.type=1;
        strcpy(dt.msg,"China");
        msgsnd(msgid,&dt,16,0);
        exit(0);
    }
    
    • 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

    自定义的结构体第一个是消息类型,读取消息是按类型进行的,0为不区分消息类型,可以全部读取。
    写入到消息队列的数据在内存中,除了删除和重启系统,不会丢失。
  • 相关阅读:
    园子的商业化努力-开篇:绝境求商
    Neo4j CQL
    AsyncContext优雅实现HTTP长轮询接口
    DASCTF X GFCTF 2022十月挑战赛
    3、CSS布局
    第14章Linux实操篇-RPM与YUM
    html如何导出为pdf格式?如何在一个js文件中,将一个函数注册到vue.prototype上?
    java-spring-mybatis -学习第一天-基础知识讲解
    Java高手的30k之路|面试宝典|精通Map篇
    使用 Apache Camel 和 Quarkus 的微服务(三)
  • 原文地址:https://blog.csdn.net/qq_63282188/article/details/132841868