【Linux】Linux应用开发:进程间通信 System V

目录

1、查看删除IPC对象

1.1 IPC对象

1.2 ipcs 命令查看系统中的 IPC 对象

1.3 ipcrm 命令删除系统中的 IPC 对象

2、共享内存

2.1 共享内存简介

2.2 共享内存相关API

2.2.1 shmget:创建共享内存

2.2.2 shmat:映射共享内存

2.2.3 shmdt:断开共享内存连接

2.2.4 shmctl:共享内存管理(删除)

2.2.5 API使用小技巧 

2.3 应用程序:共享内存 server.c

2.4 应用程序:共享内存 client.c

3、消息队列

3.1 消息队列简介

3.2 消息队列相关API

3.2.1 msgget:创建消息队列

3.2.2 msgsnd:消息队列发送

3.2.3 msgrcv:消息队列接收

3.2.4 msgctl:消息队列管理(删除)

3.3 应用程序:消息队列 server.c

3.4 应用程序:消息队列 client.c

4、信号量(信号灯)

4.1 信号量简介

4.2 信号量相关API

4.2.1 semget:创建信号量

4.2.2 semctl:控制信号量

4.2.3 semop:完成对信号量的P操作或V操作

4.2.4 init_sem:封装信号量初始化

4.2.5 sem_p:封装p操作,即获取信号量,信号量-1

4.2.6 sem_v:封装v操作,即释放信号量,信号量+1

4.2.7 semctl SETALL实例

4.2.8 semctl GETALL实例

4.3 应用程序:信号量 server.c :在程序中轮流获取与释放信号量

4.4 应用程序:信号量 client.c :在程序中查询还有多少信号量


1、查看删除IPC对象

1.1 IPC对象

        IPC(Inter-Process Communication)进程间通信,提供了各种进程间通信的方法,使用IPC通信的步骤为:

1.2 ipcs 命令查看系统中的 IPC 对象

1.ipcs -m 共享内存
2.ipcs -s 信号量
3.ipcs -q 消息队列

1.3 ipcrm 命令删除系统中的 IPC 对象

ipcrm -m id

创建的 IPC 对象如果不删除的话会一直保留在系统中 

2、共享内存

2.1 共享内存简介

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

2.2 共享内存相关API

2.2.1 shmget:创建共享内存

int shmget(key_t key, size_t size, int shmflg)
/*
功能:得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
参数:
    @key:IPC_PRIVATE 或 ftok 的返回值
    @size:共享内存区大小
    @shmflg:IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,
                        则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
             IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;
                                 如果存在这样的共享内存则报错
返回值:成功返回共享内存的标识符吗,失败返回-1
*/

2.2.2 shmat:映射共享内存

void *shmat(int shmid, const void *shmaddr, int shmflg)
/*
功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,
     随后可像本地空间一样访问
参数:
    @shmid:共享内存标识符
    @shmaddr::将共享内存映射到指定地址,若为 NULL ,则表示由系统自动完成映射
    @shmflg:SHM_RDONLY:为只读模式,其他为读写模式
返回值:成功返回映射后的地址,失败返回-1
*/

2.2.3 shmdt:断开共享内存连接

int shmdt(const void *shmaddr)
/*
功能:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
参数:
    @shmid:共享内存标识符
返回值:成功返回0,失败返回-1
*/

2.2.4 shmctl:共享内存管理(删除)

int shmctl(int shmid, int cmd, struct shmid_ds *buf)
/*
功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,
     随后可像本地空间一样访问
参数:
    @shmid:共享内存标识符
    @cmd:IPC_STAT:获取对象属性
          IPC_SET:设置对象属性
          IPC_RMID:删除这片共享内存
    @buf:指定 IPC_STAT/IPC_SET 时用以保存/设置属性
返回值:成功返回映射后的地址,失败返回-1
*/

2.2.5 API使用小技巧 

1、key_t ftok(const char *pathname, int proj_ id);

参数pathname为路径,必须是真实存在且可以访问的路径。

参数proj_id是int类型数字,且必须传入非零值。

ftok函数内部会根据路径和proj_id通过算法生成一个独一无二的key_t返回值。多进程通信时,需要通信双方使用同一个key值,因此双方使用的ftok参数应该一致。

2、shmget 的 size

该参数用于确定共享内存大小。

一般而言是4096的整数倍,因为内存的块的大小就是4KB即4096B。因此即便我们需要的空间大小不是块大小的整数倍,操作系统实际上也还是分配块的倍数个。但在使用时,那些超过size大小的多余分配空间不能访问。  

3、shmget 的 shmflg

常用的方式:

shmget(..., IPC_CREAT | 权限)                             创建失败不报错返回已有shmid

shmget(..., IPC_CREAT | IPC_EXCL | 权限)         创建失败报错返回-1

其中 IPC_EXCL 无法单独使用

通常情况下在多进程通信时,创建方使用IPC_CREAT | IPC_EXCL,接收方使用0即可。

4、shmat 的 shmaddr 和 shmflg

shmaddr一般填nullptr即可代表让内核自己确定位置。

shmflg用于确定挂接方式,一般填0

2.3 应用程序:共享内存 server.c

#define	SEG_SIZE	((size_t)100)	

int main()
{
	long now;
	int n;
	key_t key_info;
	
	if ((key_info = ftok ("/app", 'i')) < 0)
	{
		perror ("ftok info");
		exit (-1);
	}

	int seg_id = shmget(key_info, SEG_SIZE, IPC_CREAT | 0777);

	char *mem_ptr = shmat(seg_id, NULL, 0);

	for (n = 0; n < 60; n++) 
	{
		time(&now);		
		strcpy(mem_ptr, ctime(&now));	
		sleep(1);		
	}
	shmctl(seg_id, IPC_RMID, NULL);
}

2.4 应用程序:共享内存 client.c

#define	SEG_SIZE	((size_t)100)
int main()
{
	key_t key_info;
	
	if ((key_info = ftok ("/app", 'i')) < 0)
	{
		perror ("ftok info");
		exit (-1);
	}
	int seg_id = shmget(key_info, SEG_SIZE, 0777);
	
	char *mem_ptr = shmat(seg_id, NULL, 0);
	
	printf("The time, direct from memory: ..%s", mem_ptr);
	shmdt(mem_ptr);
}

3、消息队列

3.1 消息队列简介

1、消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
2、消息队列可以按照类型来发送接收消息。

3.2 消息队列相关API

3.2.1 msgget:创建消息队列

int msgget(key_t key, int flag);
/*
功能:用来创建和访问一个消息队列
参数:
    @key:和消息队列关联的 key 值
    @flag:消息队列的访问权限
返回值:成功返回消息队列ID,失败返回-1
*/

3.2.2 msgsnd:消息队列发送

int msgsnd(int msqid, const void *msgp, size_t size, int flag);
/*
功能:消息的发送
参数:
    @msqid:消息队列的 ID
    @msgp:指向消息的指针。常用消息结构 msgbuf 如下:
            struct {
                long mytype;    //消息类型
                char mtext[N];  //消息正文
            }
    @size:消息正文的字节数
    @flag: IPC_NOWAIT 消息没有发送完成函数也会立即返回。
           0:直到发送完成函数才返回
返回值:成功返回0,失败返回-1
*/

3.2.3 msgrcv:消息队列接收

int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
/*
功能:消息的接收
参数:
    @msqid:消息队列的 ID
    @msgp:接收消息的缓冲区
    @size:消息正文的字节数
    @msgtype:=0:接收消息队列中第一个消息。
              >0:接收消息队列中第一个类型为 msgtyp 的消息
              <0:接收消息队列中类型值不小于 msgtyp 的绝对值且类型值又最小的消息。
    @flag: IPC_NOWAIT:若没有消息,进程会立即返回 ENOMSG 。
           0:若无消息函数会一直阻塞
返回值:成功接收到的消息的长度,失败返回-1
*/

3.2.4 msgctl:消息队列管理(删除)

int msgctl(int msgqid, int cmd, struct msqid_ds *buf)
/*
功能:消息队列控制
参数:
    @msgqid:和消息队列关联的 key 值
    @cmd:IPC_STAT:读取消息队列的属性,并将其保存在 buf 指向的缓冲区中。
          IPC_SET:设置消息队列的属性。这个值取自 buf 参数。
          IPC_RMID:从系统中删除消息队列。
    @buf:消息队列缓冲区
返回值:成功返回0,失败返回-1
*/

3.3 应用程序:消息队列 server.c

#define QUEUE_MSG_LEN	256
#define PROJ_ID		'g'
#define PATH_NAME	"/app"
#define SERVER_MSG	1
#define CLIENT_MSG	2
struct msg 
{
	long type;
	long msgtype;
	unsigned char text[QUEUE_MSG_LEN];
};

int main(void)
{
	struct msg msg_buf;
	int qid;
	int msglen;
	int i=0;

	key_t msgkey;
	msgkey = ftok(PATH_NAME, PROJ_ID);
	qid = msgget(msgkey, IPC_CREAT | 0666);
	while (1) 
	{
		printf("server send: ");
		fgets(msg_buf.text, QUEUE_MSG_LEN, stdin);
		if (strncmp("exit", msg_buf.text, 4) == 0) 
		{
			msgctl(qid, IPC_RMID, NULL);
			break;
		}
		msg_buf.text[strlen(msg_buf.text) - 1] = '\0';
		msg_buf.type = SERVER_MSG;
		msg_buf.msgtype = i++;

		msgsnd(qid, &msg_buf, sizeof(struct msg) - sizeof(long), 0);

		msgrcv(qid, &msg_buf, sizeof(struct msg) - sizeof(long), CLIENT_MSG, 0);
		printf("server rcv: %d: %s\n",msg_buf.msgtype,msg_buf.text);		
	}
	exit(0);
}

3.4 应用程序:消息队列 client.c

#define QUEUE_MSG_LEN	256
#define PROJ_ID		'g'
#define PATH_NAME	"/app"
#define SERVER_MSG	1
#define CLIENT_MSG	2
struct msg 
{
	long type;
	long msgtype;
	unsigned char text[QUEUE_MSG_LEN];
};

int main(void)
{
	int qid;
	int msglen;
	int i=0;
	struct msg msg_buf;

	key_t msgkey;
	msgkey = ftok(PATH_NAME, PROJ_ID));
	qid = msgget(msgkey, IPC_CREAT | 0666);
	while (1) 
	{
        msgrcv(qid, &msg_buf, sizeof(struct msg) - sizeof(long), SERVER_MSG, 0);
		printf("server rcv : %ld: %s\n",msg_buf.msgtype,msg_buf.text);	

		printf("client send: ");
		fgets(msg_buf.text, QUEUE_MSG_LEN, stdin);
		if (strncmp("exit", msg_buf.text, 4) == 0) 
		{
			break;
		}
		msg_buf.text[strlen(msg_buf.text) - 1] = '\0';
		msg_buf.type = CLIENT_MSG;
		msg_buf.msgtype = i++;

		msgsnd(qid, &msg_buf, sizeof(struct msg) - sizeof(long), 0);
	}
	exit(0);
}

4、信号量(信号灯)

4.1 信号量简介

        System V 的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。属于进程间的同步与互斥机制,而关于POSIX和System V两种信号量之间的关系,可以参考以下几篇文章:

System V和Posix信号量之间的差异 |

https://www.cnblogs.com/Zoran-/p/5819256.html

Posix 信号量与System v信号量的区别_posix与system v区别_小熊诺诺的博客-优快云博客

4.2 信号量相关API

4.2.1 semget:创建信号量

int semget(key_t key, int nsems, int semflg);
/*
功能:创建信号灯集;并将信号灯集中所有灯值初始化为0
参数:
    @key:进程间通信键值,通过调用 ftok() 函数得到的键值
    @nsems:信号量的个数,一旦设置无法改变
    @semflg:IPC_CREAT:创建信号量。
             IPC_CREAT | IPC_EXCL:若信号灯集已经存在,则函数运行失败,
                                   并设置错误码errno == EEXIST;
             位或权限位:信号量位或权限位后可以设置信号量的访问权限
返回值:成功返回信号量集标识符,失败返回-1
*/

4.2.2 semctl:控制信号量

int semctl(int semid, int semnum, int cmd, ...);
/*
功能:创建信号量
参数:
    @semid:信号量集标识符
    @semnum:要修改的信号灯编号
    @cmd:见下
    @...:当有4个参数时,第4个参数为联合体
            union semun {
               short val;             //SETVAL用的值
               struct semid_ds* buf;  //IPC_STAT、IPC_SET用的semid_ds结构
               unsigned short* array; //SETALL、GETALL用的数组值
               struct seminfo *buf;   //为控制IPC_INFO提供的缓存
            } arg;
返回值:成功返回0,失败返回-1

GETVAL:获取信号量的值。此时函数有3个参数。semctl() 函数的返回值即为信号量的值。
SETVAL:设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。         
IPC_STAT:获取信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf。
IPC_SET:设置信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf。
IPC_RMID:删除信号量集。此时函数有3个参数,第2个参数semnum不起作用。
GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。
		第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
SETALL:设置信号灯集中所有信号灯的值;忽略第二个参数;
IPC_INFO:获取信号量集合的限制信息。此时函数有4个参数,第2个参数semnum不起作用。
		 第4个参数为联合体中的__buf。
GETPID:获取信号的进程号,即最后操作信号量的进程。此时函数有3个参数。
		semctl() 函数的返回值即为信号的进程号。
GETNCNT:获取等待信号的值递增的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。
GETZCNT:获取等待信号的值递减的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。
*/

4.2.3 semop:完成对信号量的P操作或V操作

int semop(int semid, struct sembuf *sops, unsigned nsops)
/*
功能:完成对信号量的P操作或V操作
参数:
    @semid:信号量集标识符
    @sops:操作信号量的结构体(struct sembuf)数组的首地址        
    @nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。
            最常见设置此值等于1,只完成对一个信号量的操作
返回值:成功返回信号量集标识符,失败返回-1

struct sembuf{
    unsigned short  sem_num;  //信号量的序号
    short       sem_op;       //信号量的操作值
    short       sem_flg;      //信号量的操作标识
};
sem_num:信号量集中信号量的序号
sem_op :
    sem_op > 0:信号量的值在原来的基础上加上此值。
    sem_op < 0:如果信号量的值小于 semop 的绝对值,则挂起操作进程。
                如果信号量的值大于等于 semop 的绝对值,
                则信号量的值在原来的基础上减去 semop 的绝对值。
    sem_op = 0:阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;
                如果信号量为0该函数,不阻塞。
                若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误
sem_flag:
    0         :阻塞方式运行, 
    IPC_NOWAIT:非阻塞方式运行,这个时候P操作不阻塞,wait for zero也不阻塞,且函数运行失败
    SEM_UNDO  :为SEM_UNDO 时,它将使操作系统跟踪当前进程对这个信号量的修改情况,
                如果这个进程在没有释放该信号量的情况下终止,
                操作系统将自动释放该进程持有的信号量。
*/

4.2.4 init_sem:封装信号量初始化

//此处初始化用的是 semctl,实际上初始化一般还是用 semget 函数
int init_sem(int semid, int num, int val)
{
	union semun myun;
	myun.val = val;
	if(semctl(semid, num, SETVAL, myun) < 0)
	{
		perror("semctl");
		exit(1);
	}
	return 0;
}

4.2.5 sem_p:封装p操作,即获取信号量,信号量-1

int sem_p(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = -1;
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0)
	{
		perror("semop");
		exit(1);
	}
	return 0;
}

4.2.6 sem_v:封装v操作,即释放信号量,信号量+1

int sem_v(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = 1;
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0)
	{
		perror("semop");
		exit(1);
	}
	return 0;
}

4.2.7 semctl SETALL实例

unsigned short setall[2] = {1, 2}; 
if(semctl(semid, 0, SETALL, setall) < 0)
{   
	perror("semctl");
	return -1; 
}   

4.2.8 semctl GETALL实例

unsigned short getall[2] = {0};
if(semctl(semid, 0, GETALL, getall) < 0)
{   
	perror("semctl");
	return -1; 
}   

4.3 应用程序:信号量 server.c :在程序中轮流获取与释放信号量

int main(void)
{
	key_t key_info;
	int semid;

	if ((key_info = ftok ("/app", 'i')) < 0) {
		perror ("ftok info");
		exit (-1);
	}

	if ((semid = semget(key_info, 1, IPC_CREAT | IPC_EXCL | 0666)) < 0) {
		if (errno == EEXIST) {
			semid = semget(key_info, 1, 0666);
		} else {
			perror ("semget");
			exit (-1);
		}
	} else {
		init_sem(semid, 0, 1);
	}

	while (1) 
	{
		printf("p\n");
		sem_p(semid, 0);
		sleep(4);
		printf("v\n");
		sem_v(semid, 0);
		sleep(3);
	}
	exit(0);
}

4.4 应用程序:信号量 client.c :在程序中查询还有多少信号量

int main(void)
{
	key_t key;  
	int semid, semval;

	if((key = ftok("/app",'i')) <0) {
		perror("ftok");
		exit(1);
	}

	if((semid  = semget(key, 1, IPC_CREAT | 0666)) < 0) {
		perror("semget");
		exit(1);
	}

	while (1) 
	{
		if ((semval = semctl(semid, 0, GETVAL, 0)) == -1)  {
			perror("semctl error!\n");
			exit(1);
		}

		if (semval > 0)  {
			printf("Still %d resources can be used\n", semval);
		} else {
			printf("No more resources can be used!\n");
		}
		sleep(1);
	}
	exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值