1.共享内存

1.1 概念

共享内存指的是操作系统在物理内存中申请一块空间,应用程序可以映射到这块空间,进行直接读写操作。

1.2 特点

  1. 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据拷贝
  2. 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
  3. 进程就可以直接读写这一块内存区域,不需要进行数据的拷贝,从而大大提高的效率
  4. 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

1.3 步骤

  1. 创建唯一key值 ftok
  2. 创建或打开共享内存 shmget
  3. 映射共享内存到用户空间 shmat
  4. 撤销映射 shmdt
  5. 删除共享内存 shmctl

1.4 函数接口

1.4.1 创建key值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:创建key值
参数: pathname: 文件名
       proj_id:取整数的低8位数据
返回值:成功:key值
        失败:-1,并设置errno错误码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main(int argc, char const *argv[])
{
    key_t key = ftok("./app", 'a');
    if(key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    return 0;
}

补充:

key值是根据pathname的inode号和proj_id的低8位组合而成的。如0x61013096
pathname只要是路径中存在的文件即可

ls -i 查看文件inode号

1.4.2 创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
    shared memory get
int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
    key  键值
    size   共享内存的大小
                创建       检测错误
    shmflg   IPC_CREAT|IPC_EXCL|0777  创建共享内存的时候的权限
返回值:成功   shmid  共享内存的id
      出错    -1

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int shmid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("%d\n", shmid);
    return 0;
}

查看创建的共享内存的命令:ipcs -m

再次执行时会出现报错的情况:
我第一次执行的时候已经创建了这个共享内存,当再一次去执行的时候,肯定会提示文件已存在,共享内存已经存在了

4.4.3 映射共享内存

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:shmid:共享内存的id号
      shmadd:一般为NULL,表示由系统自动完成映射
              如果不为NULL,那么由用户指定
      shmflg:SHM_RDONLYH就是对该共享内存进行只读操作
                  0  可读可写
返回值:成功:完成映射后的地址
        出错:(void *)-1的地址
printf("%d\n", shmid);

// 映射共享内存
char *p = (char *)shmat(shmid, NULL, 0);
if(p == (char *)-1)
{
    perror("shmat err");
    return -1;
}

// 阻塞,查看是否有没有映射成功
getchar();
return 0;
}

1.4.4 取消映射

#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:取消映射
参数:shmaddr:要取消映射的共享内存地址
返回值:成功:0
        失败:-1
// 映射共享内存
char *p = (char *)shmat(shmid, NULL, 0);
if(p == (char *)-1)
{
    perror("shmat err");
    return -1;
}

// 读写操作

// 取消映射
shmdt(p);
return 0;
}

1.4.5 删除共享内存

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:(删除共享内存), 对共享内存进行各种操作
参数:shmid  共享内存id
      cmd     IPC_STAT  获取shmid属性信息,存放在第三个参数
              IPC_SET设置shmid属性信息,要设置的属性存放在第三个参数
              IPC_RMID删除共享内存,此时第三个参数为NULL
      buf  是一个结构体指针,但是我们是删除共享内存,所以没有意义,我们直接设置为NULL就可以
返回值:成功 0
        失败 -1
// 取消映射
shmdt(p);

// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}

1.5 操作命令

ipcs -m: 查看系统中的共享内存

ipcrm -m shmid:删除共享内存

ps: 不能直接删除掉还存在进程使用的共享内存。

这时候可以用ps -ef对进程进行查看,kill掉多余的进程后,再使用ipcs查看。

练习

两个进程实现通信,一个进程输入字符串,另一个进程打印数据,当输入quit时结束

gcc input.c -o in 输入字符串

gcc output.c -o out 打印数据

./in ./out

加入标志位实现,输入一次输出一次

flag ---> 0 不可以打印(没有输入新内容)

flag---> 1 可以打印(进行了新内容输入)

struct shm{

char buf[32];

int flag;

}

struct shm *p = shmat();

input.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

struct shm
{
    char buf[32];
    int flag;
};

int main(int argc, char const *argv[])
{
    int shmid;
    char buf[32] = {};
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("%d\n", shmid);

    // 映射共享内存
    struct shm *p = (struct shm *)shmat(shmid, NULL, 0);
    if (p == (struct shm *)-1)
    {
        perror("shmat err");
        return -1;
    }

    p->flag = 0;
    // 读写操作
    while (1)
    {
        fgets(p->buf, 32, stdin);
        p->flag = 1;
        // 处理\n
        if (p->buf[strlen(p->buf) - 1] == '\n')
            p->buf[strlen(p->buf) - 1] = '\0';

        if (!strcmp(p->buf, "quit"))
            break;
    }

    // 取消映射
    shmdt(p);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}
output.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

struct shm
{
    char buf[32];
    int flag;
};

int main(int argc, char const *argv[])
{
    int shmid;
    char buf[32] = {};
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("%d\n", shmid);

    // 映射共享内存
    struct shm *p = (struct shm *)shmat(shmid, NULL, 0);
    if (p == (struct shm *)-1)
    {
        perror("shmat err");
        return -1;
    }

    p->flag = 0;
    // 读写操作
    while (1)
    {
        if(p->flag == 1)
        {
            if(!strcmp(p->buf, "quit"))
                break;
            
            printf("buf: %s\n", p->buf);

            p->flag = 0;
        }
    }

    // 取消映射
    shmdt(p);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

2.信号灯集

2.1 特点

信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;
System V信号灯集是一个或者多个信号灯集的一个集合。其中每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作

2.2 步骤

  1. 创建key值:ftok
  2. 创建或打开初始化信号灯集:semget
  3. 初始化信号灯:semctl
  4. PV操作:semop
  5. 删除信号灯集:semctl

2.3 操作命令

ipcs -s:查看信号灯集

ipcrm -s semid:删除信号灯集

2.4 函数接口

2.4.1 创建信号灯集

man 2 semget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
    nsems:信号灯集中包含的信号灯数目
    semflg:信号灯集的访问权限,
                通常为IPC_CREAT | IPC_EXCL | 0666
返回值:成功:信号灯集ID
       失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int semid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if(semid < 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 2, 0666);
        }
        else
        {
            perror("semget err");
            return -1;
        }
    }
    printf("%d\n", semid);
    return 0;
}

2.4.2 初始化或删除信号灯集

#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
功能:信号灯集的控制(初始化、删除)
参数:semid:信号灯集id
    semnum:要操作集合中的信号灯编号
    cmd:
        GETVAL:获取信号灯的值
        SETVAL:设置信号灯的值
        IPC_RMID:从系统中删除信号灯集合
    ...:当cmd为SETVAL,需要传递共用体
返回值:成功 0
        失败 -1

共用体格式:
union semun {
   int              val;    /* 信号量的初值 */
   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   unsigned short  *array;  /* Array for GETALL, SETALL */
   struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};

补充:

1.当cmd为SETVAL时需要传递第四个参数,类型为共用体。
用法:

union semun {
    int val;
};
union semun sem;
sem.val = 10;
semctl(semid, 0, SETVAL, sem); //对编号为0的信号灯设置初值为10

2.当cmd为IPC_RMID时,表示删除信号灯集
用法:semctl(semid,0,IPC_RMID)//0:表示信号灯的编号,指定任意一个即可删除

3.当cmd为GETVAL时,表示获取信号灯的值
用法:printf("%d\n",semctl(semid,0,GETVAL));

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>

union semun
{
    int val;
};

int main(int argc, char const *argv[])
{
    int semid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if(semid < 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 2, 0666);
        }
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        union semun sem;
        sem.val = 10;
        semctl(semid, 0, SETVAL, sem);  // 对编号为0的信号灯设置初值为10

        sem.val = 0;
        semctl(semid, 1, SETVAL, sem);  // 对编号为1的信号灯设置初值为0
    }
    printf("%d\n", semid);

    printf("%d\n", semctl(semid, 0, GETVAL));
    printf("%d\n", semctl(semid, 1, GETVAL));
    return 0;

2.4.3 PV操作

int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
     opsptr:操作方式
     nops:  要操作的信号灯的个数 1个
返回值:成功 :0
      失败:-1
struct sembuf {
   short  sem_num; // 要操作的信号灯的编号
   short  sem_op;  //    0 :  等待,直到信号灯的值变成0
                   //   1  :  释放资源,V操作
                   //   -1 :  分配资源,P操作                    
    short  sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};

使用:

申请资源 P操作:

mysembuf.sem_num = 0;
mysembuf.
sem_op = -1;
mysembuf.
sem_flg = 0;
semop(
semid, &mysembuf, 1);

释放资源 V操作:

mysembuf.sem_num = 0;
mysembuf.
sem_op = 1;
mysembuf.
sem_flg = 0;
semop(
semid, &mysembuf, 1);

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>

union semun
{
    int val;
};

int main(int argc, char const *argv[])
{
    int semid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 2, 0666);
        }
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        union semun sem;
        sem.val = 10;
        semctl(semid, 0, SETVAL, sem); // 对编号为0的信号灯设置初值为10

        sem.val = 0;
        semctl(semid, 1, SETVAL, sem); // 对编号为1的信号灯设置初值为0
    }
    printf("%d\n", semid);

    printf("%d\n", semctl(semid, 0, GETVAL));
    printf("%d\n", semctl(semid, 1, GETVAL));

    // p操作(申请资源)   -1
    struct sembuf buf = {0, -1, 0};
    semop(semid, &buf, 1);
    printf("0p操作:%d\n", semctl(semid, 0, GETVAL));  // 9

    // v操作(释放资源)  +1
    buf.sem_num = 1;
    buf.sem_op = 1;
    buf.sem_flg = 0;
    semop(semid, &buf, 1);
    printf("1v操作:%d\n", semctl(semid, 1, GETVAL)); // 1
    
    // 删除信号灯集
    semctl(semid, 0, IPC_RMID);
    return 0;
}

将初始化和pv操作封装成函数:

union semun
{
    int val;
};

// 初始化
void seminit(int semid, int num, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}

// pv操作
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    
    semop(semid, &buf, 1);
}
input.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>

union semun
{
    int val;
};

// 初始化
void seminit(int semid, int num, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}

// pv操作
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    
    semop(semid, &buf, 1);
}

int main(int argc, char const *argv[])
{
    int shmid, semid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
    }

    printf("%#x\n", key);

    shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);

    // 在man手册中返回值为0表示失败
    // 但是有的时候返回值为0的时候可以创建成功但是不可以使用
    if (shmid <= 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget err");
            return -1;
        }
    }

    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if (semid <= 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 2, 0666);
        }
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        seminit(semid, 0, 0);
    }

    printf("%d\n", shmid);

    // 映射共享内存
    char *p = (char *)shmat(shmid, NULL, 0);
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }

    // 读写操作
    while(1)
    {
        scanf("%s", p);
        sem_op(semid, 0, 1);
        if(!strcmp(p, "quit"))
            break;
    }
    
    // 取消映射
    shmdt(p);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

output.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>

union semun
{
    int val;
};

// 初始化
void seminit(int semid, int num, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}

// pv操作
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    
    semop(semid, &buf, 1);
}

int main(int argc, char const *argv[])
{
    int shmid, semid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
    }

    printf("%#x\n", key);

    shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);

    // 在man手册中返回值为0表示失败
    // 但是有的时候返回值为0的时候可以创建成功但是不可以使用
    if (shmid <= 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget err");
            return -1;
        }
    }

    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if (semid <= 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 2, 0666);
        }
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        seminit(semid, 0, 0);
    }

    printf("%d\n", shmid);

    // 映射共享内存
    char *p = (char *)shmat(shmid, NULL, 0);
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }

    // 读写操作
    while(1)
    {
        sem_op(semid, 0, -1);
        if(!strcmp(p ,"quit"))
            break;
        printf("%s\n", p);
    }
    
    // 取消映射
    shmdt(p);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

3.消息队列

3.1 特点

  1. 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
  2. 消息队列可以按照类型来发送/接收消息
  3. 在Linux下消息队列的大小有限制。
  • 消息队列个数最多为16个;
  • 消息队列最容量最多为16384字节;
  • 每个消息内容最多为8192字节。

3.2 步骤

  1. 创建key值
  2. 创建或打开消息队列:msgget
  3. 添加消息:msgsnd
  4. 读取消息:msgrcv
  5. 删除消息队列:msgctl

3.3 函数接口

3.3.1 创建消息队列

3.3.1 创建消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数:  key值
       flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
       失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int msgid;
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);

    msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
    if (msgid < 0)
    {
        if (errno == EEXIST)
        {
            msgid = msgget(key, 0666);
        }
        else
        {
            perror("msgget err");
            return -1;
        }
    }
    printf("%d\n", msgid);
    return 0;
}

3.3.2 添加消息

int msgsnd(int msgid, const void *msgp, size_t size, int flag); 
功能:添加消息
参数:msgid:消息队列的ID
      msgp:指向消息的指针。常用消息结构msgbuf如下:
          struct msgbuf{
            long mtype;        //消息类型
            char mtext[N]};   //消息正文
          }
   size:发送的消息正文的字节数
   flag:IPC_NOWAIT消息没有发送完成函数也会立即返回    
         0:直到发送完成函数才返回
返回值:成功:0
      失败:-1
msg_t msg;
msg.type = 1;
strcpy(msg.name, "zhangsan");
msg.age = 18;
msg.socre = 100;

msgsnd(msgid, &msg, sizeof(msg)-sizeof(long), 0);

3.3.3 读取消息

int msgrcv(int msgid,  void* msgp,  size_t  size,  long msgtype,  int  flag);
功能:读取消息
参数:msgid:消息队列的ID
     msgp:存放读取消息的空间
     size:接受的消息正文的字节数
    msgtype:0:接收消息队列中第一个消息。
            大于0:接收消息队列中第一个类型为msgtyp的消息.
            小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
     flag:0:若无消息函数会一直阻塞
        IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
      失败:-1
msg_t m;
msgrcv(msgid, &m, sizeof(m)-sizeof(long), 1, 0);
printf("%s %d %f\n", m.name, m.age, m.socre);

3.3.4 删除消息队列

int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
     cmd:
        IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
        IPC_SET:设置消息队列的属性。这个值取自buf参数。
        IPC_RMID:从系统中删除消息队列。
     buf:消息队列缓冲区
返回值:成功:0
      失败:-1
msgctl(msgid, IPC_RMID, NULL);

3.4 操作命令

ipcs -q:查看创建的消息队列

ipcrm -q [msqid]:删除消息队列

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐