进程间通信(💥IPC 对象)

共享内存

直接读写”内核的内存“,它是效率最高的进程间通信方式

  • 为了再多个进程间交换信息,内核专门留出一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
  • 由系统跳转到内核帮用户映射内核中的内存给用户(注意不是拷贝)
//表示IPC对象的key:
//为了避免生成不一样的key,用同样的pathname和proj_id只会生成一样的
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
//功能:用文件的inode节点号和用户提供的数据组合生成一个key
pathname:文件名(包含路径)
proj_id:用户提供的数据
打开、创建、共享内存对象
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//创建/打开一个共享内存对象
//返回值:成功返回ID号,失败返回-1
key:要打开或者要创建的对象的密钥
    为0或者不写就表示PRIVATE
size:要创建的共享内存大小
shmflg:打开或者创建时的权限
    IPC_CREAT:不存在则创建
    IPC_EXCL:如果存在就报错

🌴Linux中使用ipcs可以查看 消息队列 共享内存

映射空间地址
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//功能:把内核中的共享内存空间映射到用户空间
//返回值:成功返回映射后的空间地址,失败返回(void *)-1,表示0XFFFF;
shmid:共享内存的id号
shamddr:可以指定要映射的空间地址
    NULL:表示系统决定
    0X...:表示要把内核空间映射到这个地址
fhmflg:共享内存的操作权限(读写)
    SHM_RDONLY:表示只读(不用)
    0:表示可读可写
取消映射
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
//功能:把shmat映射后的空间取消掉
//返回值
控制共享内存对象
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid_ds结构体
struct shmid_ds {
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Creation time/time of last modification via shmctl() */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches */
    ...
};
//功能:整体控制共享内存对象
//返回值:成功返回0,失败返回-1
shimid:共享内存对象ID号
cmd:要执行的操作
    IPC_STAT 获取对象属性
    IPC_SET 设置对象属性
	IPC_RMID  删除对象
buf:用于设置或者获取对象的属性,如果是删除对象(IPC_RMID),写NULL
//注:删除对象并不是直接删除:而是标记这个对象为删除状态,当有一个进程标记该对象为删除后并且没有其他对象使用时,系统才正式删除该对象
练习_v1

注意:用 sudo 去运行,否则会出现permission denied

//ftok中第一个参数没有使用拥有足够权限的文件,需要加sudo
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
    key_t shmkey=ftok(".","zx");
    printf("shmkey = %d\n",shmkey);
    int shmid=shmget(shmkey,100,IPC_CREAT|0777);
    if(shmid<0){
        perror("shmget");
        return -1;
    }
    printf("shmid = %d\n",shmid);
    char *shmadd=shmat(shmid,NULL,0);
    if(shmadd == (void *)-1){
        perror("shmat");
        return -1;
    }
    fgets(shmadd,64,stdin);
    printf("shmadd = %s\n",shmadd);
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}
练习_v2
//ftok中第一个参数使用带有足够权限的文件,就不需要加sudo
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
    key_t shmkey=ftok("a.txt",122234);
    printf("shmkey = %d\n",shmkey);
    int shmid=shmget(shmkey,100,IPC_CREAT|0777);
    if(shmid<0){
        perror("shmget");
        return -1;
    
    printf("shmid = %d\n",shmid);
    char *shmadd=shmat(shmid,NULL,0);
    if(shmadd == (void *)-1){
        perror("shmat");
        return -1;
    }
    fgets(shmadd,64,stdin);
    printf("shmadd = %s\n",shmadd);
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}
关闭共享内存、信号量数组

ipcrm [选项] ipcrm shm|msg|sem …

移除某个 IPC 资源。

选项: -m, --shmem-id 按 id 号移除共享内存段 -M, --shmem-key <键> 按键值移除共享内存段 -q, --queue-id 按 id 号移除消息队列 -Q, --queue-key < 键 > 按键值移除消息队列 -s, --semaphore-id 按 id 号移除信号量 -S, --semaphore-key < 键 > 按键值移除信号量 -a, --all [=<shm|msg|sem>] (将指定类别中的) 全部移除 -v, --verbose 解释正在进行的操作

-h, --help display this help -V, --version display version

信号灯集合

二值信号灯:值为 0 或者 1。

创建、打开信号灯集对象
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
//功能:创建或者打开一个信号灯集对象
//返回值:成功返回ID号,失败返回-1
key:要打开或者创建对象时的“密钥”
nesms:指明要创建的信号量集包含的信号量个数。如果只是打开信号量,把nsems设置为0即可。该参数只在创建信号量集时有效。
semflg为操作标识,可取如下值:
0:取信号量集标识符,若不存在则函数会报错
IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错
上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

信号灯集的操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
//结构体:
struct sembuf
{
  unsigned short int sem_num;   /* semaphore number */
  short int sem_op;             /* semaphore operation */
  short int sem_flg;            /* operation flag */
};
//功能:申请或者释放资源
//返回值:成功返回0,失败返回-1
	semid:信号灯集ID

信号灯集的整体控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../*union semun arg*/);
//功能:整体返回控制信号
//返回值:成功返回0,失败返回-1
semid:要操作的id号
semnum:要修改的信号灯编号(type)
cmd:要执行的指令
    GETVAL:获取信号灯的值
    SETVAL:设置信号灯的值
    IPC_RMID:从系统中删除信号灯集合
//第四个参数的共用体
    union semun {
    int              val;    /* Value for SETVAL */
    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) */
	};
//如果需要设置值,那么会有第四个参数(共用体),这个共用体里的val成员表示给信号灯初始化的个数
//🔑注:如果是删除,不需要共用体。只需要再semnum这个参数随便写一个信号灯集的编号就能整体删完对象

练习
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main(int argc,char *argv[])
{
        key_t key=ftok("a.txt",111);
        if(key<0){
                perror("ftok");
                return -1;
        }
        printf("key= %d \n",key);
        /*2. create se,*/
        int semid =semget(key,3,IPC_CREAT | 0666);
    		//3表示有3种信号量
        if(semid<0){
                perror("semget");
                return -1;
        }
        printf("sem id = %d\n",semid);
        return 0;
}

结果如图

image-20230404150445905

💊综合示例:代码,包括进程、信号灯、共享内存等内容的例子

消息队列

例子
  • 两个终端发消息,类似qq
//client_a.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <pthread.h>
typedef struct msgbuf{
	long mtype;
	char mtext[512];
}MSG;
#define LEN (sizeof(MSG)-sizeof(long))
//send massage
void send_msg(int msgid,char *buf){
	int ret=-1;
	if(( ret=msgsnd(msgid,buf,LEN,0))<0){
		perror("msgsnd");
	}
}
void* receive_msg(){
	key_t key=ftok("aa.txt",'a');
	MSG msgbuf;
	memset(msgbuf.mtext,-1,512);
	int qid=msgget(key,IPC_CREAT|0664);
	while(1){
		int ret=msgrcv(qid,&msgbuf,LEN,1,0);
		if(ret<0){
			perror("msgrcv");
		
		}else{
			printf("%s\n",msgbuf.mtext);
		}
	}
}

int main(){
	MSG msgbuf;
	msgbuf.mtype=2;
	memset(msgbuf.mtext,-1,512);
	key_t key=ftok("aa.txt",'a');
	if(key<0){
		perror("ftok");
		return -1;
	}
	pthread_t pt;
	pthread_create(&pt,NULL,receive_msg,NULL);
	int msgid=-1;
	if(msgid=msgget(key,IPC_CREAT|0664)<0){
		perror("msgget");
		return -1;
	}
	while(1){
	
		fgets(msgbuf.mtext,LEN,stdin);
		send_msg(msgid,&msgbuf);
	
	}
	return 0;
}
//client_b.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <pthread.h>
typedef struct msgbuf{
	long mtype;
	char mtext[512];
}MSG;
#define LEN (sizeof(MSG)-sizeof(long))
//send massage
void send_msg(int msgid,char *buf){
	int ret=-1;
	if(( ret=msgsnd(msgid,buf,LEN,0))<0){
		perror("msgsnd");
	}
}
void receive_msg(){
	key_t key=ftok("aa.txt",'a');
	MSG msgbuf;
	memset(msgbuf.mtext,-1,512);
	int qid=msgget(key,IPC_CREAT|0664);
	while(1){
		int ret=msgrcv(qid,&msgbuf,LEN,2,0);
		if(ret<0){
			perror("msgrcv");
		
		}else{
			printf("%s\n",msgbuf.mtext);
		}
	}
}

int main(){
	MSG msgbuf;
	msgbuf.mtype=1;
	memset(msgbuf.mtext,-1,512);
	key_t key=ftok("aa.txt",'a');
	if(key<0){
		perror("ftok");
		return -1;
	}
	pthread_t pt;
	pthread_create(&pt,NULL,receive_msg,NULL);
	int msgid=-1;
	if(msgid=msgget(key,IPC_CREAT|0664)<0){
		perror("msgget");
		return -1;
	}
	while(1){
	
		fgets(msgbuf.mtext,LEN,stdin);
		send_msg(msgid,&msgbuf);
	
	}
	return 0;
}