目录

  • 一、信号灯集
  • 1.1 概念
  • 1.2 信号灯集创建步骤⭐⭐⭐
  • 1.3 信号灯集对应函数
  • 1.4 练习
  • 二、消息队列
  • 2.1 特点
  • 2.2 消息队列的创建步骤
  • 2.3 函数
  • 2.4 练习


一、信号灯集

1.1 概念

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

1.2 信号灯集创建步骤⭐⭐⭐

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

1.3 信号灯集对应函数

  1. semget
    int semget(key_t key, int nsems, int semflg);
  • 功能:创建/打开信号灯
  • 参数:
  • key:ftok产生的key值
  • nsems:信号灯集中包含的信号灯数目
  • semflg:信号灯集的访问权限,通常为
    IPC_CREAT | IPC_EXCL |0666
  • 返回值:
  • 成功:信号灯集ID
  • 失败:-1

例:创建或打开信号灯集 ↓

Linux编程——进程间通信(信号灯集、消息队列)_嵌入式开发


运行:

Linux编程——进程间通信(信号灯集、消息队列)_进程间通信_02

🚨注意:由于系统原因,第一次创建信号灯集,semid为0,不能用,要排除。故第一次创建完之后要删除semid为0的信号灯集,重新创建。
→查看系统当前信号灯集:ipcs -s
→删除系统信号灯集:ipcrm -s semid

  1. semctl
    int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
  • 功能:信号灯集合的控制(初始化/删除)
  • 参数:
  • semid:信号灯集ID
  • semnum: 要操作的集合中的信号灯编号
  • cmd:信号灯集的控制方式
  • GETVAL:获取信号灯的值,返回值是获得值
  • SETVAL:设置信号灯的值(初始化信号灯),需要用到第四个参数:共用体
  • IPC_RMID:从系统中删除信号灯集合(如有多个信号灯,最后只删除任意一个信号灯即可将所有的信号灯删除)
  • 返回值:成功 0;失败 -1

用法:第四个参数是一个共用体,需自己创建,共用体第一个参数就是信号灯的初值

Linux编程——进程间通信(信号灯集、消息队列)_linux_03


一般,该共用体只用到第一个成员变量,信号灯集的初始化代码可如下:

union semun{
    int val;
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
  1. semop
    int semop (int semid, struct sembuf *opsptr, size_t nops);
  • 功能:对信号灯集合中的信号量进行PV操作
  • 参数:
  • semid:信号灯集ID
  • opsptr:操作方式,注意是一个结构体指针
  • nops: 要操作的信号灯的个数1个
  • 返回值:成功 :0;失败:-1

struct sembuf结构体成员:

struct sembuf {
       short  sem_num; // 要操作的信号灯的编号
       short  sem_op;  
            // 0 :  等待,直到信号灯的值变成0
            // 1 :  释放资源,V操作
            //-1 :  申请资源,P操作                    
       short  sem_flg; 
            // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};

例:信号灯创建整个流程👇

#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[])
{
#if 1
    //1.创建key值
    key_t key = ftok(".", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("key:%d\n", key);
    //2.创建或打开信号灯集
    int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666); //创建信号灯集,包含2个信号灯
    if (semid <= 0)
    {
        if (errno == EEXIST) //存在则只打开
            semid = semget(key, 2, 0666);
        else
        {
            perror("semget err");
            return -1;
        }
    }
#endif
#if 1
    else                 //3.初始化。若信号灯集已经创建好,则不需要初始化
    {                    // 所以要放到else里面
        union semun sem; //共用体变量

        sem.val = 0;
        semctl(semid, 0, SETVAL, sem);

        sem.val = 5;
        semctl(semid, 1, SETVAL, sem);
    }
    printf("semid:%d\n", semid);
    printf("0:%d\n", semctl(semid, 0, GETVAL));
    printf("1:%d\n", semctl(semid, 1, GETVAL));
    //4.pv操作
    struct sembuf buf;
    //v
    buf.sem_num = 0; //信号灯编号
    buf.sem_op = 1;  //sem.val+1
    buf.sem_flg = 0; //阻塞:当信号的值减为0时,申请不到资源,阻塞
    semop(semid, &buf, 1); //1:1个灯
    //p
    buf.sem_num = 1; //信号灯编号
    buf.sem_op = -1; //sem.val-1
    buf.sem_flg = 0; //阻塞:当信号的值减为0时,申请不到资源,阻塞
    semop(semid, &buf, 1);

    printf("0:%d\n", semctl(semid, 0, GETVAL));
    printf("1:%d\n", semctl(semid, 1, GETVAL));
    //5.删除信号灯集
    semctl(semid, 0, IPC_RMID);
#endif
    return 0;
}
  1. 对部分信号灯集创建步骤进程简单 函数封装
    将信号灯集的初始化和PV操作进行函数的封装,目的是为了代码结构的模块化,使得代码不显得过于啰嗦,对于部分操作的代码实现只需要一次编写便可以对此调用,减少代码编写的重复工作。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>

union semun {
    int val;
};
void myinit(int semid,int num,int val) //3.信号灯集初始化函数
{
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}
void semop(int semid,int num,int op) //4.pv操作函数
{
    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[])
{
    //创建key值
    key_t key = ftok(".", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("key:%d\n", key);
    //2.创建或打开信号灯集
    int 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
    {//3.初始化
        myinit(semid,0,0);
        myinit(semid,1,5);
    }
    printf("semid:%d\n", semid);
    //4.pv操作
    semop(semid,0,1);
    semop(semid,1,-1);

    printf("0:%d\n", semctl(semid, 0, GETVAL));
    printf("1:%d\n", semctl(semid, 1, GETVAL));
    //5.删除信号灯集
    semctl(semid, 0, IPC_RMID);
    return 0;
}

1.4 练习

两个进程实现通信(分文件编写),一个进程循环从终端输入,另一个进程循环打印,输入一次打印一次,当输入quit时结束。封装函数,使用用共享内存加信号灯集实现。

/***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>
#include <sys/sem.h>

union semun {
    int val;
};
void myinit(int semid,int num,int val)
{
    //3.初始化
    union semun sem;

    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}
void sem_op(int semid,int num,int op)
{//4.pv操作
    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[])
{
    //创建key值
    key_t key;
    key = ftok(".", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }

    //创建或打开共享内存
    int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid <= 0) //=0不使用
    {
        //若已经创建,给shmid重新赋值
        if (errno == 17)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    //映射
    char *p = shmat(shmid, NULL, 0);
    if (p == (void *)-1)
    {
        perror("shmat err");
        return -1;
    }

    //2.创建或打开信号灯集
    int 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
    {//3.初始化
        myinit(semid,0,0);
    }
    printf("semid:%d\n", semid);  

    //写操作
    while (1)
    {
        fgets(p, 32, stdin); //将终端输入放到共享内存
        sem_op(semid,0,1); //v
        printf("0:%d\n", semctl(semid, 0, GETVAL));
        if (strcmp(p, "quit\n") == 0)
        {
            break;
        }
    }
    //5.删除信号灯集
    semctl(semid, 0, IPC_RMID);
    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>
#include <sys/sem.h>

union semun {
    int val;
};
void myinit(int semid,int num,int val)
{
    //3.初始化
    union semun sem;

    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}
void sem_op(int semid,int num,int op)
{//4.pv操作
    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[])
{
    //创建key值
    key_t key;
    key = ftok(".", 'a');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }

    //创建或打开共享内存
    int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid <= 0) //=0不使用
    {
        //若已经创建,给shmid重新赋值
        if (errno == 17)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    //映射
    char *p = shmat(shmid, NULL, 0);
    if (p == (void *)-1)
    {
        perror("shmat err");
        return -1;
    }
    //2.创建或打开信号灯集
    int 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
    // {//3.初始化
    //     myinit(semid,0,0);
    // }
    printf("semid:%d\n", semid);  


    while (1)
    {
        printf("0:%d\n", semctl(semid, 0, GETVAL));
        sem_op(semid,0,-1);//p
        if (strcmp(p, "quit\n") == 0)
        {
            break;
        }
        fputs(p,stdout); //共享内存内容输出到终端
        //printf("0:%d\n", semctl(semid, 0, GETVAL));
    }
    //5.删除信号灯集
    semctl(semid, 0, IPC_RMID);
    return 0;
}

二、消息队列

2.1 特点

  • 消息队列是IPC对象的一种
  • 消息队列由消息队列ID来唯一标识
  • 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
  • 消息队列可以按照类型来发送(添加)/接收(读取)消息

2.2 消息队列的创建步骤

  1. 创建key值 ftok
  2. 创建或打开消息队列 msgget
  3. 添加消息:按照类型将消息添加到已打开的消息队列的末尾 msgsnd
  4. 读取消息:可以按照类型将消息从消息队列中读走 msgrcv
  5. 删除消息队列 msgctl

2.3 函数

  1. msgget
    int msgget(key_t key, int flag);
  • 功能:创建或打开一个消息队列
  • 参数:
  • key值
  • flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
  • 返回值:成功:msgid;失败:-1
  1. msgsnd
    int msgsnd(int msqid, const void *msgp, size_t size, int flag);
  • 功能:往消息队列中添加消息
  • 参数:
  • msqid:消息队列的ID
  • msgp:指向消息的指针,指向一个消息结构体(需自己创建)

    →常用消息结构msgbuf如下:
    struct msgbuf{
    long mtype; //消息类型
    char mtext[N] //消息正文
    };
  • size:发送的消息正文的字节数(即除了结构体消息类型的剩余大小)。
  • flag:
  • IPC_NOWAIT消息没有发送完成函数也会立即返回
  • 0:直到发送完成函数才返回
  • 返回值:成功:0;失败:-1
    使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
    🚨注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
  1. msgrcv
    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
  1. msgctl
    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)

2.4 练习

  1. 单个消息读取


    若将msgrcv的倒数第二个参数msgtype改为0,也可以打印出类型1的消息,原因是msgrcv中msgtype为0时,可以匹配任意类型消息,且默认读取消息队列中第一个消息。
  2. 多个消息读取