IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【Linux】进程间通信——消息队列、共享内存与信号量 -> 正文阅读

[系统运维]【Linux】进程间通信——消息队列、共享内存与信号量

标识符与键key

?内核中的IPC结构(消息队列、信号量、共享内存)都用一个非负整数的标识符加以引用。标识符是IPC对象的内部名,为使多个进程能够使用同一IPC对象,需要提供一个外部命名方案。为此,每个IPC对象都与一个键key相关联,将这个键作为该对象的外部名。创建IPC结构时都应指定一个键,这个键的数据类型是基本系统数据类型key_t,这个键由内核变换成标识符。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
// 返回值:若成功返回键key,失败返回(key_t)-1

参数:

  1. pathname:当前操作系统中一个存在的路径,使用该文件属性的st_dev和st_ino填充键
  2. proj_id:产生键时,使用该参数的低8位
    在这里插入图片描述
// 该结构规定了权限和所有者
struct ipc_perm
{
	__kernel_key_t	key;	// key
	__kernel_uid_t	uid;	// 所有者ID
	__kernel_gid_t	gid;	// 所属组ID
	__kernel_uid_t	cuid;	// 创建者进程ID
	__kernel_gid_t	cgid;	// 创建者进程组ID
	__kernel_mode_t	mode; 	// 读写权限
	unsigned short	seq;	// 序号
};

消息队列

消息队列是消息的链表,存放在内核中,由消息队列标识符标识。

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于进程。进程终止后,消息队列及其内容并不会被删除。
  • 消息队列的消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
    在这里插入图片描述
    内核为每个消息队列维护了一个结构体,数据结构如下:
struct msqid_ds {
	struct ipc_perm msg_perm;	// ipc_perm权限等信息
    
	struct msg *msg_first;		/* 指向消息队列头 */
	struct msg *msg_last;		/* 指向消息队列尾 */
    
	__kernel_time_t msg_stime;	/* 最后发送消息的时间 */
	__kernel_time_t msg_rtime;	/* 最后接收消息的时间 */
	__kernel_time_t msg_ctime;	/* 最后修改的时间 */
    
	unsigned long  msg_lcbytes;	/* 重用32位垃圾字段 */
	unsigned long  msg_lqbytes;	/* 重用32位垃圾字段 */
    
	unsigned short msg_cbytes;	/* 当前队列大小 */
	unsigned short msg_qnum;	/* 当前队列的消息个数 */
	unsigned short msg_qbytes;	/* 队列的最大字节数 */
	__kernel_ipc_pid_t msg_lspid;	/* 最后mesgsnd的pid*/
	__kernel_ipc_pid_t msg_lrpid;	/* 最后recevice的pid*/
};

消息的数据结构如下:

struct msg_msg 
{
	struct list_head m_list;	
	long m_type;		// 消息的类型
	size_t m_ts;		// 消息的大小
	struct msg_msgseg *next;	// 下一个节点
	void *security;		// 真正的消息的位置
};

创建/获取消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//返回值,若成功返回消息队列ID,失败返回-1

参数:

  1. key:消息队列的键key
  2. msgflg:创建消息队列时指定的属性
  • IPC_CREAT:创建新的消息队列,同时需要指定对消息队列的操作权限
  • IPC_EXCL:检测消息队列是否存在,必须和IPC_CREAT一起使用

操作消息队列

#include <sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds* buf);
//返回值:成功返回0,失败返回-1

参数:

  1. shmid:消息队列ID
  2. cmd
  • IPC_STAT:得到当前消息队列的状态
  • IPC_SET:设置消息队列的状态
  • IPC_RMID:删除该消息队列以及仍在该队列中的所有数据,删除立即生效。仍在使用这一队列的其他进程,再次操作时,会得到EIDRM错误
  1. buf:
    cmd=IPC_STAT,作为传出参数,会得到消息队列的相关属性信息
    cmd=IPC_SET,作为传入参数,将用户的自定义属性设置到消息队列中
    cmd=IPC_RMID,buf无意义,指定为NULL即可

发送消息

#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// 成功返回0 , 失败返回 -1

参数:

  1. msqid:消息队列ID
  2. msgp:指向自定义缓冲区
struct msgbuf	{   	
	long mtype;			// 消息的类型
	char mtext[512]; 	// 消息内容,在使用时,自己重新定义此结构
};
  1. msgsz:待发送的消息内容的长度,即mtext的长度
  2. msgflg
  • 0:阻塞式函数。进程解除阻塞:有空间可以容纳要发送的消息;从系统中删除此队列;捕捉到一个信号
  • IPC_NOWAIT:类似文件I/O的非阻塞I/O标志,若消息队列已满,或者队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值,则函数立即出错,返回EAGAIN

接收消息

#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// 成功返回消息数据部分的长度,出错返回-1
  1. msqid:消息队列ID
  2. msgp:与msgsnd函数一致
  3. msgsz:数据缓冲区的长度
  4. msgtype:从消息队列中取出哪一类型的消息
  • type=0:返回队列中的第一个消息。
  • type>0:返回队列中消息类型为type的第一个消息。
  • type<0:返回队列中消息类型值小于等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
  1. msgflg参数
  • 0:阻塞式函数。
  • IPC_NOWAIT:类似文件I/O的非阻塞I/O标志。
  • MSG_NOERROR:若返回的消息长度大于msgsz,则该消息会被截断,系统不会通知。若没有设置这一标志,则出错返回E2BIG,消息仍留在队列中。

示例

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

// 自定义消息
struct msg_buf
{
    long mtype;      // 消息类型
    char mtext[512]; // 消息缓冲区
};

int main(int argc, char *argv[])
{
    key_t key;
    struct msg_buf msg_buf_snd = {1, "1-hello world!"};
    struct msg_buf msg_buf_recv;
    memset(&msg_buf_recv, 0, sizeof(msg_buf_recv));
    // 获取Key
    key = ftok("./key.c", 0xFF);
    if (key == -1)
    {
        perror("ftok");
        return -1;
    }
    // 创建消息队列
    int msgid = msgget(key, 0777 | IPC_CREAT | IPC_EXCL);
    if (msgid == -1)
    {
        // 若消息队列已存在
        if (errno = EEXIST)
        {
            msgid = msgget(key, 0777);
            if (msgid == -1)
                goto MSG_GET_ERR;
        }
        else
        {
            goto MSG_GET_ERR;
        }
    }
    printf("msg id :%d\n", msgid);

    /* 消息队列数据收发 */
    int ret = 0;
    for (int i = 0; i < 10; i++)
    {
        ret = msgrcv(msgid, &msg_buf_recv, sizeof(msg_buf_recv.mtext), 2, 0);
        if (ret == -1)
        {
            perror("msgrecv");
            return -1;
        }
        printf("msgrecv: %s\n", msg_buf_recv.mtext);
        ret = msgsnd(msgid, &msg_buf_snd, strlen(msg_buf_snd.mtext), 0);
        if (ret == -1)
        {
            perror("msgsnd");
            return -1;
        }
    }
    return 0;
MSG_GET_ERR:
    perror("msgget");
    return -1;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <string.h>
// 自定义消息
struct msg_buf
{
    long mtype;      // 消息类型
    char mtext[512]; // 消息缓冲区
};

int main(int argc, char *argv[])
{
    key_t key;
    struct msg_buf msg_buf_recv;
    struct msg_buf msg_buf_snd = {2, "2-hello world!"};
    memset(&msg_buf_recv, 0, sizeof(msg_buf_recv));
    // 获取Key
    key = ftok("key.c", 0xFF);
    if (key == -1)
    {
        perror("ftok");
        return -1;
    }
    //创建消息队列
    int msgid = msgget(key, 0777 | IPC_CREAT | IPC_EXCL);
    if (msgid == -1)
    {
        // 若消息队列已存在
        if (errno = EEXIST)
        {
            msgid = msgget(key, 0777);
            if (msgid == -1)
                goto MSG_GET_ERR;
        }
        else
            goto MSG_GET_ERR;
    }
    printf("msg id :%d\n", msgid);
    /*消息队列数据收发*/
    int ret = 0;
    for (int i = 0; i < 10; i++)
    {
        ret = msgsnd(msgid, &msg_buf_snd, strlen(msg_buf_snd.mtext), 0);
        if (ret == -1)
        {
            perror("msgsnd");
            return -1;
        }
        ret = msgrcv(msgid, &msg_buf_recv, sizeof(msg_buf_recv.mtext), 1, 0);
        if (ret == -1)
        {
            perror("msgrecv");
            return -1;
        }
        printf("msgrecv: %s\n", msg_buf_recv.mtext);
    }
    return 0;
MSG_GET_ERR:
    perror("msgget");
    return -1;
}

共享内存

? ? ? ?共享内存允许两个或多个进程共享同一块存储区,通过地址映射将这块物理内存映射到不同进程的地址空间中,多个进程可以通过这块物理空间进行数据的交互,达到进程间通信的目的。

  • 共享内存既可以实现有血缘关系的进程间通信也可以实现没有血缘关系的进程间通信。
  • 共享内存不属于任何进程,并且不受进程生命周期的影响。
  • 共享内存操作默认不阻塞,共享内存不保证进程间的数据同步。
  • 当共享内存被标记为删除状态之后,并不会马上被删除,直到所有的进程全部与共享内存解除关联(引用计数原理),共享内存才会被删除。
  • 共享内存是最快的进程间通信方式,进程间通信不涉及到内核,不用通过系统调用来传递数据。
    在这里插入图片描述

内核为每块共享内存维护着一个结构体,数据结构如下:

struct shmid_ds {
	struct ipc_perm		shm_perm;	/* 操作权限 */
	int					shm_segsz;	/* 共享内存段的大小,单位:字节 */
	__kernel_time_t		shm_atime;	/* 最后挂载时间 */
	__kernel_time_t		shm_dtime;	/* 最后分离时间 */
	__kernel_time_t		shm_ctime;	/* 最后更改时间 */
	__kernel_ipc_pid_t	shm_cpid;	/* 创建者pid */
	__kernel_ipc_pid_t	shm_lpid;	/* 最后操作的进程pid */
	unsigned short		shm_nattch;	/* 当前连接数 */
	unsigned short 		shm_unused;	/* compatibility */
	void 				*shm_unused2;	/* ditto - used by DIPC */
	void				*shm_unused3;	/* unused */
};

创建/获取共享内存

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
// 返回值:成功返回共享内存ID,失败返回-1

参数:

  1. key:共享内存的键key
  2. size:创建共享内存时,指定共享内存的大小(单位:字节),而如果引用一个已存在的共享内存,则将size指定为0。
  3. shmflg:创建共享内存时指定的属性
  • IPC_CREAT:创建新的共享内存,指定对共享内存的操作权限
  • IPC_EXCL:检测共享内存是否存在,必须和IPC_CREAT一起使用

连接共享内存到当前进程的地址空间

#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 返回值:连接成功,返回共享内存的起始地址,连接失败返回(void *)-1

参数:

  1. shmid:共享内存ID
  2. shmaddr:共享内存的起始地址,指定为NULL,让内核指定
  3. shmflg:对共享内存的操作权限
  • SHM_RDONLY:读权限
  • 0:读写权限

断开与共享内存的连接

#include <sys/shm.h>
int shmdt(const void *shmaddr);
// 返回值:成功返回0,失败返回-1

参数:

  1. shmaddr:共享内存的起始地址

操作共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// 返回值:函数调用成功返回0,调用失败返回-1

参数:

  1. shmid:共享内存ID
  2. cmd
  • IPC_STAT:得到当前共享内存的状态
  • IPC_SET:设置共享内存的状态
  • IPC_RMID:标记共享内存为删除状态
  1. buf:
  • cmd=IPC_STAT,作为传出参数,会得到共享内存的相关属性信息
  • cmd=IPC_SET,作为传入参数,将用户的自定义属性设置到共享内存中
  • cmd=IPC_RMID,buf无意义,指定为NULL即可

相关shell命令

// 查看系统中共享内存的详细信息
ipcs -m

? ? ? ?当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的键key从一个正整数变为 0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。当共享内存被标记为删除状态并且这个引用计数变为0之后,共享内存才会被真正的被删除。
在这里插入图片描述

示例

// 写进程
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
    // 1. 创建共享内存, 大小为4k
    int shmid = shmget(1000, 4096, IPC_CREAT | 0664);
    if (shmid == -1)
    {
        perror("shmget error");
        return -1;
    }

    // 2. 当前进程和共享内存关联
    void *ptr = shmat(shmid, NULL, 0);
    if (ptr == (void *)-1)
    {
        perror("shmat error");
        return -1;
    }

    // 3. 写共享内存
    const char *p = "hello world, 共享内存真香。";
    memcpy(ptr, p, strlen(p) + 1);

    // 阻塞程序
    printf("按任意键继续, 删除共享内存\n");
    getchar();

    // 进程和共享内存解除关联
    shmdt(ptr);

    // 删除共享内存
    // 当共享内存被标记为删除状态之后,并不会马上被删除,直到所有的进程全部和共享内存解除关联,共享内存才会被删除。
    shmctl(shmid, IPC_RMID, NULL);
    printf("共享内存已经被删除...\n");

    return 0;
}
// 读进程
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
    // 1. 打开已经存在的共享内存
    int shmid = shmget(1000, 0, 0);
    if (shmid == -1)
    {
        perror("shmget error");
        return -1;
    }

    // 2. 当前进程和共享内存关联
    void *ptr = shmat(shmid, NULL, 0);
    if (ptr == (void *)-1)
    {
        perror("shmat error");
        return -1;
    }

    // 3. 读共享内存
    printf("共享内存数据: %s\n", (char *)ptr);

    // 阻塞程序
    printf("按任意键继续, 删除共享内存\n");
    getchar();

    // 进程和共享内存解除关联
    shmdt(ptr);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    printf("共享内存已经被删除...\n");

    return 0;
}

信号量

信号量与以上IPC结构不同,它是一个计数器,信号量用于进程间的互斥与同步,若要在进程间传递数据需要结合共享内存。

  • 信号量基于操作系统的P/V操作,程序对信号量的操作都是原子操作,是在内核中实现。
  • 对信号量的PV操作不仅限于对信号量值+1或-1,可以加减任意正整数。
    在这里插入图片描述
    内核为每个信号量集合维护了一个结构体,数据结构如下:
struct semid_ds 
{
	struct   ipc_perm sem_perm; /* IPC权限 */
	long     sem_otime; 		/* 最后一次对信号量操作(semop)的时间 */
    long     sem_ctime; 		/* 对这个结构最后一次修改的时间 */
	struct   sem *sem_base; 	/* 在信号量数组中指向第一个信号量的指针 */
	struct   sem_queue *sem_pending; 		/* 待处理的挂起操作*/
	struct   sem_queue **sem_pending_last; 	/* 最后一个挂起操作 */
	struct   sem_undo *undo;	/* 在这个数组上的undo请求,用于恢复初始信号量值 */
    ushort   sem_nsems; 		/* 在信号量数组上的信号量编号 信号量的数量*/
};

单个信号量的数据结构如下:

struct sem { 
   	unsigned short semval; 	/* 信号量的值 */
	pid_t sempid; 			/* 在信号量上最后一次操作的进程号 */
	unsigned short semncnt;/* 待信号量的值递增的进程数 */
	unsigned short semzcnt;/* 等待信号量的值递减的进程数 */
};

创建/获取信号量集合

#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
// 返回值:成功返回信号量ID,失败返回-1

参数:

  1. key:信号量集合的键key
  2. nsems:如果是创建新信号量集合,那么nsems代表新信号量集合中的信号量的数目。如果是获取当前存在的信号量集合,那么此设置参数为0。
  3. semflg:创建信号量时指定的属性
  • IPC_CREAT:创建新的信号量,指定对信号量的操作权限
  • IPC_EXCL:检测信号量是否存在,必须和IPC_CREAT一起使用

设置信号量集合

# include<sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
// 返回值:成功返回0,失败返回-1
  1. semid: 信号量集合ID
  2. semunm: 某一信号量在信号量集合中对应的序号
  3. cmd: 用来指定对信号量集合的操作
  • SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
  • IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
union semun
{
   int              val;   // 信号量的值
   struct semid_ds* buf;
   unsigned short* array; 
   struct seminfo* _buf;
};

操作信号量集合

# include<sys/sem.h>
int semop(int semid, struct sembuf* sops, unsigned nsops);
// 返回值:成功返回0,失败返回-1

参数:

  1. semid: 信号量集合ID
  2. sops:指针指向一个sembuf类型的结构体,指明对某信号量的操作。
struct sembuf{
     unsigned short sem_num;	// 该信号量在信号量集合中对应的序号,[0, sem_nums-1]
     short          sem_op; 	// 信号量值在一次操作中的改变量
     short          sem_flg;	// 操作标识,IPC_NOWAIT、SEM_UNDO(当进程退出时,会将信号量置为初始值)
}
  1. nsops:操作的信号量的个数

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <errno.h>
// 联合体
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// 初始化信号量集合
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if (semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    // 该信号量在信号量集合中对应的序号
    sbuf.sem_num = 0;
    // P操作
    sbuf.sem_op = -1;
    sbuf.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    // 该信号量在信号量集合中对应的序号
    sbuf.sem_num = 0;
    // V操作
    sbuf.sem_op = 1;
    sbuf.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集合
int del_sem(int sem_id)
{
    union semun tmp;
    if (semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

int main()
{
    // 获取key
    key_t key = ftok("./key.c", 0xFF);
    if (key == -1)
    {
        perror("ftok");
        return -1;
    }

    //  创建信号量集合,其中只有一个信号量
    int sem_id = semget(key, 1, 0777 | IPC_CREAT | IPC_EXCL);
    if (sem_id == -1)
    {
        if (errno == EEXIST)
        {
            int sem_id = semget(key, 0, 0777);
            if (sem_id == -1)
            {
                perror("semget error");
                return -1;
            }
        }
        else
        {
            perror("semget error");
            return -1;
        }
    }
    // 初始化信号量,初始值为0
    init_sem(sem_id, 0);

    // 创建子进程
    pid_t pid = fork();
    if (pid == -1)
    {
        perror("Fork Error");
    }
    // 子进程
    else if (pid == 0)
    {
        sleep(2);
        printf("Process child: pid=%d\n", getpid());
        // V操作,释放资源
        sem_v(sem_id);
    }
    // 父进程
    else
    {
        // P操作,等待资源
        sem_p(sem_id);
        printf("Process father: pid=%d\n", getpid());
        // V操作,释放资源
        sem_v(sem_id);
        // 删除信号量集合
        del_sem(sem_id);
    }
    return 0;
}

参考:https://subingwen.cn/linux/shm/
参考:https://www.cnblogs.com/CheeseZH/p/5264465.html
参考:https://blog.csdn.net/weixin_43937576/article/details/116599068

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-06 11:20:15  更:2022-05-06 11:21:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 17:18:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码