Linux进程通信——共享内存和信号量

共享内存

共享内存是最快的进程通信方式。

因为不同于消息队列和管道,需要调用内核函数切换运行环境,而是直接在自己进程的虚拟地址空间里操作。
这里写图片描述

  • 创建或打开共享内存

int shmget(key_t key, int size, int flag);

key:共享内存字段名

size:共享内存大小

flag:和创建文件的mode相同

返回值:返回共享内存标识码

  • 将共享内存映射到进程地址空间

void *shmat(int shmid, const void *shmaddr, int shmflag)

shmid:共享内存标识

shmaddr:指定连接地址,一般为NULL

shmflag:一般为0

返回值:返回映射到虚拟地址空间的起始位置

  • 取消内存映射

int shmdt(const void *shmaddr)

shmid:shmat返回的地址

  • 删除共享内存

shmctl(shmid, IPC_RMID, 0)

在命令行删除

ipcrm -M key 删除共享内存

ipcs -m 查看共享内存

信号量集

信号量的本质是计数器

struct semaphore{
    int value;
    struct task_struct queue;//需求该信号控制的资源的队列
}


p(struct semaphore s){
    s.value--;
    if(value < 0){
        进程阻塞,加入等待队列
    }
}

v(struct semaphore s){
    s.value++;
    if(s.value <=0 ){
        //说明曾经有至少一个进程在等
        唤醒某个等待的进程
    }
}

生产者消费者模型
这里写图片描述

死锁:多个进程访问互斥资源,每个进程都拿到一部分资源,如果要继续往下运行,就要拿到更多资源,而更多的资源由其他进程占有,每个进程在执行完毕之前不会释放已占有资源,形成环路等待。

必要条件

  1. 资源排他性
  2. 请求保持
  3. 环路等待
  4. 不可剥夺

银行家算法:https://blog.csdn.net/hanzheng6602/article/details/78765673

哲学家进餐:
这里写图片描述

Linux SystemV 信号量

//自定义的信号量
union semnum{
    int val;
}
union semnu su;
//获取信号量集
int semget(key_t key, int nsems, int flag);
nsems:信号量个数
flag:IPC_CREATE || 0
//控制
int semctl(int id, int semnum, int cmd, ...);
cmd:SETVAL设置初值,有第四个参数su || GETVAL获得初值

/*pv操作*/
int semop(int id, struct sembuf *sb, int len);
//sb参数是对信号量的操作定义
struct sembuf{
    int sem_num;
    int sem_op;//v是1, p是-1
    int sem_flag;
}

pv操作例

#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int main(){
    //获得信号量集标识
    int id = semget(1234, 1, IPC_CREATE|0644);
    if (-1 == id)
        perror();
    struct sembuf sb[1] = {{0,-10}};
    //执行一次p操作
    semop(id, sb, 1);
}

利用信号量实现两个进程的互斥

任务:父进程打印XX,子进程打印OO,在XX或者OO是一个整体,在没有打印完成对的X或者O不允许另一个进程打印。

解决:将打印XX上锁,用一个互斥信号量。

#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <stdio.h>
int id;
void p(void)
{
    struct sembuf sb[1] = {{0,-1,0}};
    semop(id, sb, 1);
}

void v(void)
{
    struct sembuf sb[1] = {{0,1,0}};
    semop(id, sb, 1);
}

void print(char c)
{
    int i;
    for(i=0; i<10; i++){
        p();
        printf("%c", c);
        fflush(stdout);
        sleep(rand()%3);
        printf("%c", c);
        fflush(stdout);
        v();
        sleep(rand()%3);
    }
}

union semnu{int val;};
int main()
{
    //创建有一个信号量的信号量集
    id = semget(1234, 1, IPC_CREAT|0600);
    union semnu su;
    su.val = 1;
    //给0号信号量赋初值
    semctl(id, 0, SETVAL, su);
    pid_t pid = fork();
    if(0 == pid){
        //子进程打印
        print('O');
    }
    else{
        print('X');
    }
}

利用信号量保证共享内存的同步问题

pg1程序往共享内存不断写入数字,pg2不断读出数字。要保证pg2每次只读出最新写入的数字。

#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
union semnu{int val;};
int main()
{
    int shmid = shmget(1234, sizeof(int), IPC_CREAT|0600);

    int semid = semget(1234, 1, IPC_CREAT|0600);
    union semnu su;
    su.val = 0;
    semctl(semid, 0, SETVAL, su);
    int *p = (int*)shmat(shmid, NULL, 0);
    int num = 1;
    struct sembuf sb[1] = {{0, 1, 0}};
    while(1){
        sleep(1);
        *p = num++;
        semop(semid, sb, 1);
    }
}
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main()
{
    int shmid = shmget(1234, 0, 0);
    int semid = semget(1234, 1, 0);

    int *p = (int*)shmat(shmid, NULL, 0);
    struct sembuf sb[1] = {{0, -1, 0}};
    while(1){
        semop(semid, sb, 1);
        printf("%d\n", *p);
    }
}

执行结果

[root@localhost 6_3]# ./pg2
3
3
3
4
5
6
7
8

pg1先执行了3秒,所以现在信号量的值被v增加到3,pg2后执行直接先读取了3次然后两个进程同步起来。

猜你喜欢

转载自blog.csdn.net/hanzheng6602/article/details/80537206
今日推荐