【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文件实现创建一个信号

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>

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");
    }

}

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

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

a进程代码

c 复制代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#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;
}

b进程代码

c 复制代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#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;
}

效果如下: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. *

测试代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
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);
}

结果如图:

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

代码如下:

sem.c

c 复制代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/sem.h>

#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");
    }
}

read.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#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();
}

write.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#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.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 //从消息队列中读取

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
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);
}

msgcreat.c //写入数据

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
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);
}

自定义的结构体第一个是消息类型,读取消息是按类型进行的,0为不区分消息类型,可以全部读取。

写入到消息队列的数据在内存中,除了删除和重启系统,不会丢失。

相关推荐
qq_2430507913 分钟前
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
linux·web安全·网络安全·黑客·教程·kali linux·voip工具
饭碗、碗碗香29 分钟前
【开发常用命令】:docker常用命令
linux·运维·笔记·学习·docker·容器
懒羊羊大王呀1 小时前
Ubuntu20.04中MySQL的安装和配置
linux·mysql·ubuntu
浅安的邂逅1 小时前
Ubuntu apt-get安装-报错:尝试“apt --fix-broken install”有未能满足的依赖关系,几种解决办法
linux·ubuntu·apt install
爱瑞瑞2 小时前
🐧深入浅出的认识 Linux 指令
linux·shell
ajassi20002 小时前
开源 java android app 开发(十一)调试、发布
android·java·linux·开源
小李飞刀李寻欢3 小时前
使用kubeadm部署Kubernetes(k8s)集群的步骤
linux·服务器·ubuntu·kubernetes·k8s
运维成长记3 小时前
阿里云实践创建实例步骤
linux·运维·服务器·阿里云·云计算
THe CHallEnge of THe BrAve3 小时前
Linux检验库是否安装成功
linux·运维·服务器
算家计算4 小时前
告别复杂文档解析噩梦!MonkeyOCR 本地部署教程:支持公式/表格多元素结构化
linux·人工智能·开源