【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为不区分消息类型,可以全部读取。

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

相关推荐
_extraordinary_3 小时前
Linux基本指令(三)+ 权限
linux·运维·服务器
WalkingWithTheWind~3 小时前
Linux搭建Nginx直播流媒体服务RTMP/RTSP转Http-flv视频浏览器在线播放/Vue/Java/ffmpeg
java·linux·nginx·ffmpeg·vue·http-flv·rtsp
和舒貌5 小时前
Linux系统编程基础详解
linux·运维·服务器·基础
Jack魏6 小时前
Linux MySQL 8.0.29 忽略表名大小写配置
linux·mysql·mysql8·mysql常见问题
ctrigger6 小时前
AI回答:Linux C/C++编程学习路线
linux·c语言·c++
蛊明7 小时前
下载CentOS 10
linux·运维·centos
北京-宏哥8 小时前
Linux系统安装MySQL5.7(其他版本类似)避坑指南
linux·运维·服务器
Aphelios3808 小时前
Linux 下 VIM 编辑器学习记录:从基础到进阶(下)
java·linux·学习·编辑器·vim
qw9498 小时前
Linux 高级篇 日志管理、定制自己的Linux系统、备份与恢复
linux·运维·服务器
丶只有影子8 小时前
【Nacos】从零开始启动Nacos服务(windows/linux)
linux·运维·windows·微服务·springcloud