【系统编程】进程间通信 --IPC(消息队列msg、共享内存shm、信号量sem)

linux系统编程大纲
1. 进程概念、进程诞生与死亡、进程函数接口、进程意义。
2. 进程通信方式,有名管道、无名管道、信号、消息队列、共享内存、信号量。
3. 进程信号集、如何设置阻塞属性。
4. 线程概念,线程与进程区别?线程诞生与死亡,函数接口。
5. 线程同步互斥方式,有名信号量,无名信号量,互斥锁,读写锁,条件变量。
6. 拓展 -> 线程池  -> 同时处理多个任务。
 


目录

一. IPC对象

二. 如何创建IPC对象?

三、进程之间的通信  -> 消息队列

四. 进程之间的通信  -> 共享内存

五、 信号量


一. IPC对象

1、 什么是IPC对象?
在linux系统,消息队列/共享内存/信号量都是属于IPC对象。例子: 假设创建一条消息队列,等于创建了一个IPC对象。

  动作                      结果
访问文件      ->       文件描述符
创建消息队列  ->    消息队列ID   -> 代表相对应的IPC对象
创建共享内存  ->    共享内存ID
创建信号量    ->      信号量ID

2. 在linux下,如何查看IPC对象的ID号?
gec@ubuntu:~$ ipcs -a

Shared Memory Segments  -> 共享内存
key        shmid      owner      perms      bytes      nattch     status  
        共享内存ID

Semaphore Arrays -> 信号量
key        semid      owner      perms      nsems
          信号量ID 

Message Queues  -> 消息队列
key        msqid      owner      perms      used-bytes   messages   
       消息队列ID

3. 如何删除IPC对象?
因为IPC对象ID号唯一的,所以可以根据IPC对象ID号删除。
删除共享内存: ipcrm -m 共享内存ID
删除消息队列: ipcrm -q 消息队列ID
删除信号量:   ipcrm -s 信号量ID

二. 如何创建IPC对象?

1、 IPC对象ID由key值来决定,也就是key值一致,那么根据key来申请ID号,ID号也会一致。
    也就是说申请ID号之前,首先要申请一个key值。

2. 如何申请key值?  -> ftok()  -> man 3 ftok
功能: 获取IPC对象的key值
       convert a pathname and a project identifier to a System V IPC key  
       提供一个路径和一个数字,那么就会返回IPC对象的key值。

使用格式:
        #include <sys/types.h>
        #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);

      pathname:一个合法并且存在的文件  "."
      proj_id:非0整数  0~128   例如: 10

     只要路径/整数有一个不一致,那么返回值就会不一样。

     返回值:    
        成功:申请到的key值
        失败:-1

   练习: 验证"只要路径/整数有一个不一致,那么返回值就会不一样"

#include "head.h"

int main()
{
    key_t k1,k2,k3,k4;
    
    k1 = ftok(".",10);
    k2 = ftok("..",10);
    k3 = ftok(".",20);
    k4 = ftok(".",10);
    
    printf("k1 = %d\n",k1);  //与k4相同
    printf("k2 = %d\n",k2);  //k2与k1/k3都不相同
    printf("k3 = %d\n",k3);  //k3与k1/k2都不相同
    printf("k4 = %d\n",k4);  //与k1相同
}

  结论: 如果两个任意的进程需要访问同一个IPC对象,那么这两个进程申请key值必须参数一致!

三、进程之间的通信  -> 消息队列

消息队列特征: 读取特性类型的数据。
管道特性: 有什么数据,只能读取什么数据。

消息队列程序思路
1、 为消息队列IPC对象申请key值  -> ftok()
2、 根据申请到的key值去申请一个新的ID号   -> msgget()  -> man 2 msgget

        include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>

       int msgget(key_t key, int msgflg);

    key: 申请的key值
    msgflg:  IPC_CREAT  -> 不存在则创建  -> 需要添加起始权限: IPC_CREAT|0666
                 IPC_EXCL  -> 存在则报错

    返回值:
        成功:消息队列的ID号    
        失败:-1

3、 往消息队列中写入数据   -> msgsnd()  -> man 2 msgsnd
    
        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>

   int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

    msqid:消息队列的ID号
    msgp:数据缓冲区  -> 包含类型以及正文

struct msgbuf{    -> 需要在代码中重新声明一个这样的结构体,正文大小由用户自己设定!
       long mtype;      -> 消息的类型
       char mtext[1];   -> 消息的正文  -> 自定义的结构体/数组
};

    msgsz:消息正文的大小
    msgflg: 正常普通属性填0

    返回值:
        成功:0
        失败:-1

4、 读取消息队列的数据  ->  msgrcv()  -> man 2 msgrcv

        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>

    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

    msqid:消息队列的ID号
    msgp:数据缓冲区  -> 包含类型以及正文

struct msgbuf{    -> 需要在代码中重新声明一个这样的结构体,正文大小由用户自己设定!
       long mtype;      -> 消息的类型
       char mtext[1];   -> 消息的正文  -> 自定义的结构体/数组
};
    msgsz:消息正文的大小
    msgtyp:需要读取的消息的类型
    msgflg: 正常普通属性填0

    返回值:
        成功: 实际读取到的消息正文的字节数
        失败: -1

5、删除消息队列  ->  msgctl()  -> man 2 msgctl

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

    int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    msqid:消息队列的ID号
    cmd:  IPC_RMID  ->  删除消息队列
    buf: 如果是获取属性,则需要填一个指针struct msqid_ds *
         如果是删除,则只需要NULL

    返回值:
        成功:0
        失败:-1

  例题: 使用消息队列,使得Jack进程可以发送数据给Rose进程。

Rose进程

#include "head.h"

struct msgbuf{
    long mtype;
    char mtext[50];
};

int main(int argc,char *argv[])
{
    //1. 为消息队列IPC对象申请key值
    key_t key = ftok(".",10);
    
    //2. 根据key值申请消息队列的ID
    int msgid = msgget(key,IPC_CREAT|0666);
    
    //3. 从消息队列中读取数据
    struct msgbuf gec;
    int ret;
    
    while(1)
    {
        bzero(&gec,sizeof(gec));
        ret = msgrcv(msgid,&gec,sizeof(gec.mtext),10,0);
        if(ret == -1)
        {
            printf("msgrcv error!\n");
            exit(-1);
        }
        
        printf("from Jack:%s",gec.mtext);
        
        if(strncmp(gec.mtext,"quit",4) == 0)
            break;
    }
    
    //4. 删除消息队列
    msgctl(msgid,IPC_RMID,NULL);
    
    return 0;
}

Jack进程

#include "head.h"

struct msgbuf{
    long mtype;
    char mtext[50];
};

int main(int argc,char *argv[])
{
    //1. 为消息队列IPC对象申请key值
    key_t key = ftok(".",10);
    
    //2. 根据key值申请消息队列的ID
    int msgid = msgget(key,IPC_CREAT|0666);
    
    //3. 不断往消息队列中写入数据
    struct msgbuf gec;
    int ret;
    
    while(1)
    {
        bzero(&gec,sizeof(gec));
        gec.mtype = 10;  //消息的类型
        fgets(gec.mtext,50,stdin);  //消息的正文  quit
        
        ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
        if(ret == -1)
        {
            printf("msgsnd error!\n");
            exit(-1);
        }
        
        if(strncmp(gec.mtext,"quit",4) == 0)
        {
            break;
        }
    }
    
    return 0;
}

四. 进程之间的通信  -> 共享内存

1、 共享内存的机制?
      两个进程根据key与ID号申请一片内存空间,两个进程只需要访问同一片内存空间就可以互相通信。

2、 如何使用共享内存实现两个进程之间通信?
操作思路:
1)为共享内存IPC对象申请key值。  -> ftok()
2)根据key值申请共享内存的ID号。  -> shmget()
3)根据ID号在内存空间中映射出一片内存空间。 -> shmat()
4)根据内存空间的地址进行交互。
5)撤销内存空间。   -> shmdt()
6)删除共享内存对应的IPC对象。 -> shmctl()

3、实现共享内存的函数接口?


1)根据key值申请共享内存的ID号  -> shmget()  -> man 2 shmget
功能: 获取共享内存ID  allocates a shared memory segment
使用格式:
        #include <sys/ipc.h>
        #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);
 
    key:申请的key值
    size:必须是PAGE_SIZE倍数  PAGE_SIZE=1024  
    shmflg:
        IPC_CREAT  -> 不存在则创建  -> 需要添加起始权限: IPC_CREAT|0666
        IPC_EXCL  -> 存在则报错

    返回值:
        成功:共享内存的ID号
        失败:-1

2)根据ID号在内存空间中映射出一片内存空间?  -> shmat()   -> man 2 shmat
功能:产生内存空间
使用格式:
        #include <sys/types.h>
        #include <sys/shm.h>

       void *shmat(int shmid, const void *shmaddr, int shmflg);
    
    shmid:共享内存的ID号 
    shmaddr:  NULL -> 系统分配一片未使用的内存空间。
        不为NULL -> 用户自己决定地址
    shmflg:普通属性  -> 0

    返回值:
        成功:共享内存的起始地址
        失败:(void *)-1

3)撤销内存空间   -> shmdt()  -> man 2 shmdt
功能:撤销映射
使用格式:
        #include <sys/types.h>
        #include <sys/shm.h>

      int shmdt(const void *shmaddr);

    shmaddr: 需要撤销内存空间起始地址

      返回值:
        成功:0
        失败:-1


 4) 删除共享内存对应的IPC对象  -> shmctl()  -> man 2 shmctl
使用格式:
        #include <sys/ipc.h>
        #include <sys/shm.h>

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    shmid:共享内存的ID号
    cmd:  IPC_RMID  ->  删除消息队列
    buf: 如果是获取属性,则需要填一个指针struct msqid_ds *
         如果是删除,则只需要NULL

    返回值:
        成功:0
        失败:-1


    练习:使用共享内存实现两个进程之间通信。 Jack -> Rose

读取内存代码:

#include "head.h"

int main()
{
    //1. 申请key值
    key_t key = ftok(".",10);
    
    //2. 申请ID
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    
    //3. 根据ID号申请内存空间
    char *p = (char *)shmat(shmid,NULL,0);
    
    //4. 不断获取共享内存空间
    while(1)
    {
        printf("%s",p);
        usleep(500000);
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    
    //5. 撤销映射
    shmdt(p);
    
    //6. 删除IPC对象
    shmctl(shmid,IPC_RMID,NULL);
    
    return 0;
}

写入数据:

#include "head.h"

int main()
{
    //1. 申请key值
    key_t key = ftok(".",10);
    
    //2. 申请ID
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    
    //3. 根据ID号申请内存空间
    char *p = (char *)shmat(shmid,NULL,0);
    
    //4. 不断从键盘中获取数据,然后输入内存中
    while(1)
    {
        fgets(p,1024,stdin); //"hello"
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    
    return 0;
}

  结果: 无论共享内存中有没有数据的变化,都会不断打印出来数据出来   -> 数据践踏
     有数据变化  -> 打印
     无数据变化  -> 阻塞     -> 信号量

五、 信号量

1、 什么是信号量?
      进程之间通信方式  -> 有名管道,无名管道,信号,消息队列,共享内存。
     信号量不是一种通信方式,它只是作用于通信方式,作用: 处理同步互斥

2、 关于信号量使用函数接口?
1)根据key值申请ID号  -> semget()  -> man 2 semget
功能: get a semaphore set identifier
使用格式:
       #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>

       int semget(key_t key, int nsems, int semflg);

    key: 信号量IPC对象的key值
    nsems: 信号量集合中元素的个数  -> 空间/数据 ->2
    semflg: IPC_CREAT|0666  -> 不存在则创建
                 IPC_EXCL  -> 存在则报错

    返回值:
        成功:信号量ID
        失败:-1

2)信号量如何实现P/V操作?   ->  semop()  ->  man 2 semop

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

       int semop(int semid, struct sembuf *sops, unsigned nsops);

    semid: 信号量ID
    sops:  进行P/V操作的结构体的指针

该结构体:
struct sembuf{
    unsigned short sem_num;  操作信号量元素的下标   空间->0  数据->1    
        short          sem_op;   P操作/V操作   p -> -1   v  -> 1          
        short          sem_flg;  普通属性 -> 0
}

    nsops: 信号量操作结构体的个数 ->1

    返回值:
        成功:0
        失败:-1

   能P/V,马上返回。
   不能P/V,只能阻塞。

3)设置信号量属性  ->  semctl()   ->  man 2 semctl
  
       #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>

       int semctl(int semid, int semnum, int cmd, ...);

    semid:需要操作的信号量的ID号
    semnum:    操作信号量元素的下标  空间->0  数据->1   
    cmd:SETVAL   -> 用于设置信号量元素的起始值  
             IPC_RMID -> 删除信号量
    ...: 空间/数据起始值,删除填NULL!

  例子: 设置有空间  -> semctl(semid,0,SETVAL,1);

    返回值:
        成功:0
        失败:-1

   练习: 把信号量加入到共享内存中

发送方:

#include "head.h"

int main()
{
	//1. 为共享内存与信号量申请key值
	key_t key1 = ftok(".",10); //共享内存
	key_t key2 = ftok(".",20); //信号量
	
	//2. 为共享内存与信号量申请ID
	int shmid = shmget(key1,1024,IPC_CREAT|0666); //共享内存
	int semid = semget(key2,2,IPC_CREAT|0666); //信号量
	
	//3. 根据ID号申请内存空间
	char *p = (char *)shmat(shmid,NULL,0);
	
	//4. 设置信号量起始值  写入: 有车位(空间为1),无车(数据为0)
	semctl(semid,0,SETVAL,1);  //有车位
	semctl(semid,1,SETVAL,0);  //无车
	
	struct sembuf space;
	space.sem_num = 0;  //空间
	space.sem_op = -1;  //P操作
	space.sem_flg = 0;
	
	struct sembuf data;
	data.sem_num = 1;  //数据
	data.sem_op = 1;  //v操作
	data.sem_flg = 0;
	
	//5. 不断从键盘中获取数据,然后输入内存中
	while(1)
	{
		//请问车位能否减1,能 -> 开车进内存空间  不能 -> 阻塞等待
		semop(semid,&space,1);  //想空间P操作  -> 能:返回   不能:阻塞
		
		fgets(p,1024,stdin); // 把车开进内存空间
		
		//想车的数量+1
		semop(semid,&data,1);
		
		if(strncmp(p,"quit",4) == 0)
		{
			break;
		}
	}
	
	return 0;
}

接收方:

#include "head.h"

int main()
{
	//1. 为共享内存与信号量申请key值
	key_t key1 = ftok(".",10); //共享内存
	key_t key2 = ftok(".",20); //信号量
	
	//2. 为共享内存与信号量申请ID
	int shmid = shmget(key1,1024,IPC_CREAT|0666); //共享内存
	int semid = semget(key2,2,IPC_CREAT|0666); //信号量
	
	//3. 根据ID号申请内存空间
	char *p = (char *)shmat(shmid,NULL,0);
	
	//4. 设置信号量起始值  写入: 有车位(空间为1),无车(数据为0)
	semctl(semid,0,SETVAL,1);  //有车位
	semctl(semid,1,SETVAL,0);  //无车
	
	struct sembuf data;
	data.sem_num = 1;  //数据
	data.sem_op = -1;  //p操作
	data.sem_flg = 0;
	
	struct sembuf space;
	space.sem_num = 0;  //空间
	space.sem_op = 1;  //v操作
	space.sem_flg = 0;
	
	//4. 不断获取共享内存空间
	while(1)
	{
		//请问有没有车可以开出来?(数据能不能减1)  能: 返回  不能: 阻塞
		semop(semid,&data,1);  //资源数-1
		
		printf("%s",p);  //能,说明数据能减1,马上开走
		
		semop(semid,&space,1);  //资源数-1
		
	//	usleep(500000);
		if(strncmp(p,"quit",4) == 0)
		{
			break;
		}
	}
	
	//5. 撤销映射
	shmdt(p);
	
	//6. 删除IPC对象
	shmctl(shmid,IPC_RMID,NULL);
	
	return 0;
}

综合题:

jack:


#include "head.h"

struct msgbuf{
	long mtype;
	char mtext[50];
};

#define J2R 10
#define R2J 20

void fun(int sig)
{
	printf("catch sig = %d\n",sig);
	exit(0); //父进程发送了quit给Rose
}

int main()
{
	//1. 申请key值
	key_t key = ftok(".",10);
	
	//2. 根据key值申请消息队列ID
	int msgid = msgget(key,IPC_CREAT|0666);
	
	//3. 带着这条队列,产生一个子进程
	pid_t x;
	x = fork();
	if(x > 0)
	{
		signal(SIGCHLD,fun);
		
		struct msgbuf gec;
		int ret;
		while(1)
		{
			bzero(&gec,sizeof(gec));
			gec.mtype = J2R;
			fgets(gec.mtext,50,stdin);
			
			ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
			if(ret == -1)
			{
				printf("msgsnd error!\n");
				exit(-1);
			}
			
			if(strncmp(gec.mtext,"quit",4) == 0)  //Jack发送了quit给Rose,那么Jack的子进程也应该要退出!
			{
				kill(x,SIGUSR1);
				exit(0); //正常退出
			}
		}
	}
	
	if(x == 0)
	{
		signal(SIGUSR1,fun);
		
		struct msgbuf gec;
		int ret;
		while(1)
		{
			bzero(&gec,sizeof(gec));
			ret = msgrcv(msgid,&gec,sizeof(gec.mtext),R2J,0);
			if(ret == -1)
			{
				printf("msgrcv error!\n");
				exit(-1);
			}
			
			printf("from Rose :%s",gec.mtext);
			
			if(strncmp(gec.mtext,"quit",4) == 0)  //子进程如果收到了quit,就不要发送数据给父进程了
			{
				msgctl(msgid,IPC_RMID,NULL);
				exit(0);  //正常退出  -> 自动会发送SIGCHLD给父进程
			}
		}	
	}
}

rose:


#include "head.h"

struct msgbuf{
	long mtype;
	char mtext[50];
};

#define J2R 10
#define R2J 20

void fun(int sig)
{
	//printf("catch sig = %d\n",sig);
	exit(0); //父进程发送了quit给Rose
}

int main()
{
	//1. 申请key值
	key_t key = ftok(".",10);
	
	//2. 根据key值申请消息队列ID
	int msgid = msgget(key,IPC_CREAT|0666);
	
	//3. 带着这条队列,产生一个子进程
	pid_t x;
	x = fork();
	if(x > 0)
	{
		signal(SIGCHLD,fun);
		
		struct msgbuf gec;
		int ret;
		while(1)
		{
			bzero(&gec,sizeof(gec));
			gec.mtype = R2J;
			fgets(gec.mtext,50,stdin);
			
			ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
			if(ret == -1)
			{
				//printf("msgsnd error!\n");
				exit(-1);
			}
			
			if(strncmp(gec.mtext,"quit",4) == 0)  //Jack发送了quit给Rose,那么Jack的子进程也应该要退出!
			{
				kill(x,SIGUSR1);
				exit(0); //正常退出
			}
		}
	}
	
	if(x == 0)
	{
		signal(SIGUSR1,fun);
		
		struct msgbuf gec;
		int ret;
		while(1)
		{
			bzero(&gec,sizeof(gec));
			ret = msgrcv(msgid,&gec,sizeof(gec.mtext),J2R,0);
			if(ret == -1)
			{
				//printf("msgrcv error!\n");
				exit(-1);
			}
			
			printf("from Jack :%s",gec.mtext);
			
			if(strncmp(gec.mtext,"quit",4) == 0)  //子进程如果收到了quit,就不要发送数据给父进程了
			{
				msgctl(msgid,IPC_RMID,NULL);
				exit(0);  //正常退出  -> 自动会发送SIGCHLD给父进程
			}
		}	
	}
}
发布了64 篇原创文章 · 获赞 82 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40602000/article/details/101267933
今日推荐