Linux系统编程与网络编程——POSIX IPC信号量,消息队列,共享内存(十三)

前言

POSIX(Portable Operating System Interface)可移植操作系统接口,IPC机制(Inter-Process Communication,进程间通信),POSIX IPC主要包括消息队列、信号量、共享内存。


POSIX信号量

POSIX信号量有两种:有名信号量无名信号量无名信号量也被称作基于内存的信号量。有名信号量通过IPC名字进行进程间的同步,而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步,线程同步将在后面讲到,这里我们重点研究有名信号量。

创建一个信号量

创建的过程还要求初始化信号量的值。根据信号量取值(代表可用资源的数目)的不同,POSIX信号量还可以分为:

1.二值信号量:信号量的值只有0和1,这和互斥量很类型,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;

2.计数信号量:信号量的值在0到一个大于1的限制值(POSIX指出系统的最大限制值至少要为32767)。该计数表示可用的资源的个数。
在这里插入图片描述
在这里插入图片描述

等待一个信号量

该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。

函数 sem_wait() 用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减去1,表明公共资源经使用后减少,其函数原型是:
在这里插入图片描述
成功返回0,错误返回-1,设置errno

函数sem_trywait()与函数sem_wait()的区别是当信号量的值等于0时,sem_trywait()函数不会阻塞当前线程。
在这里插入图片描述
成功返回0,错误返回-1,设置errno,当没等到资源的时候,errno为EAGAIN

释放一个信号量

该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。

函数 sem_post() 用来增加信号量的值,其函数原型是:
在这里插入图片描述
综合例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>

void error_print(const char* msg)
{
	perror(msg);
	exit(-1);
} 

int main(int argc, char** argv)
{
	int ret;
	sem_t* sem = sem_open("/mysem", O_CREAT, 0666, 1);
	if(sem == NULL)
		error_print("sem_open");
	puts("wait semahpore...\n");
	sem_wait(sem);
	puts("get");
	sleep(5);
	puts("post");
	sem_post(sem);
	sem_close(sem);
	return 0;
}

编译时需要加上-lpthread:
在这里插入图片描述
运行两次该程序,观察现象。


POSIX消息队列

消息队列可以认为是一个链表。进程(线程)可以往里写消息,也可以从里面取出消息。一个进程可以往某个消息队列里写消息,然后终止,另一个进程随时可以从消息队列里取走这些消息。这里也说明了,消息队列具有随内核的持续性,也就是系统不重启,消息队列永久存在

创建(并打开)、关闭、删除一个消息队列
在这里插入图片描述
需要注意:
消息队列的名字最好使用“/”打头,并且只有一个“/”的名字。否则可能出现移植性问题; 它必须符合已有的路径名规则(最多由PATH_MAX个字节构成,包括结尾的空字节)。

POSIX消息队列

消息队列的读写主要使用下面两个函数:
在这里插入图片描述
源码sendmq.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mqueue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

void error_print(const char* msg)
{
	perror(msg);
	exit(-1);
} 

/*向消息队列发送消息,消息队列名及发送的信息通过参数传递*/
int main(int argc, char *argv[])
{
	const char* mqname = "/mymq";
	char buf[] = "helloworld";
	mqd_t mqd;
	int ret;
	//只写模式找开消息队列,不存在就创建
	mqd = mq_open(mqname, O_WRONLY | O_CREAT, 0666, NULL);
	if(mqd < 0)
		error_print("mq_open");
	/*向消息队列写入消息,如消息队列满则阻塞,直到消息队列有空闲时再写入*/
	ret = mq_send(mqd, buf, strlen(buf) + 1, 10);
	if(ret < 0)
		error_print("mq_send");
		
	ret = mq_close(mqd);
	if(ret < 0)
		error_print("mq_close");
	return 0;
}

编译:
在这里插入图片描述
运行后可以在/dev/mqueue下看到mymq文件。

源码recvmq.c

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

void error_print(const char* msg)
{
	perror(msg);
	exit(-1);
} 

/*读取某消息队列,消息队列名通过参数传递*/
int main(int argc, char *argv[])
{
	const char* mqname = "/mymq";
	mqd_t mqd;
	struct mq_attr attr;
	char *buf;
	unsigned int prio;
	int ret;
	/*只读模式打开消息队列*/
	mqd = mq_open(mqname, O_RDONLY);
	if(mqd < 0)
		error_print("mq_open");
	/*取得消息队列属性,根据mq_msgsize动态申请内存*/
	ret = mq_getattr(mqd, &attr);
	if(ret < 0)
		error_print("mq_getattr");
	/*动态申请保证能存放单条消息的内存*/
	buf = (char*)malloc(attr.mq_msgsize);
	if(NULL == buf)
		error_print("malloc");
	/*接收一条消息*/
	ret = mq_receive(mqd, buf, attr.mq_msgsize, &prio);
	if(ret < 0)
		error_print("receive");
	printf("read data %s priority %u\n", buf, prio);
	ret = mq_close(mqd);
	if(ret < 0)
		error_print("mq_close");
	/*消息队列使用完后就可以删除
	ret = mq_unlink(mqname);
	if(ret < 0)
		error_print("mq_unlink");
	*/
	free(buf);
	return 0;
}

编译并执行:
在这里插入图片描述
需要注意的是:
当消息不断发送,达到消息队列容量最大值的时候,mq_send将阻塞,直到消息队列被接收走,如果消息还未接收,就把消息队列文件删除,则消息丢失。

消息队列的属性
在这里插入图片描述
获取消息队列的属性:
attrmq.c

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

void error_print(const char* msg)
{
	perror(msg);
	exit(-1);
} 
int main()
{
	mqd_t mqd;
	int ret;
	struct mq_attr mqattr;
	const char* mqname = "/mymq";
	
	/*只读模式打开消息队列*/
	mqd = mq_open(mqname, O_CREAT | O_RDONLY, 0666, NULL);
	if(mqd < 0)
		error_print("mq_open");
		
	/*取得消息队列属性*/
	ret = mq_getattr(mqd, &attr);
	if(ret < 0)
		error_print("mq_getattr");
		
	printf("nonblock flag:%ld\n", attr.mq_flags);
	printf("max msgs:%ld\n", attr.mq_maxmsg);
	printf("max msg size:%ld\n", attr.mq_msgsize);
	printf("current msg count:%ld\n", attr.mq_curmsgs);
	
	ret = mq_close(mqd);
	if(ret < 0)
		error_print("mq_close");
	ret = mq_unlink(mqname);
	if(ret < 0)
		error_print("mq_unlink");
	return 0;
}

编译并执行:
在这里插入图片描述
设置消息队列的属性:
源码setarrtmq.c

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

void error_print(const char* msg)
{
	perror(msg);
	exit(-1);
} 

int main()
{
	mqd_t mqd;
	int ret;
	const char * mqname = "/mymq";
	struct mq_attr mqattr;
	mqattr.mq_maxmsg = 10;
	mqattr.mq_msgsize = 8192;
	mqd = mq_open(mqname, O_RDWR | O_CREAT, 0666, &mqattr);
	
	if(mqd < 0)
	{
		perror("mq_open");
		exit(1);
	} 
	
	mqattr.mq_flags = O_NONBLOCK;
	mq_setattr(mqd, &mqattr, NULL);// mq_setattr()只关注mq_flags
	/*取得消息队列属性*/
	ret = mq_getattr(mqd, &mqattr);
	if(ret < 0)
		error_print("mq_getattr");
		
	printf("nonblock flag:%ld\n", mqattr.mq_flags);
	printf("max msgs:%ld\n", mqattr.mq_maxmsg);
	printf("max msg size:%ld\n", mqattr.mq_msgsize);
	printf("current msg count:%ld\n", mqattr.mq_curmsgs);
	
	ret = mq_close(mqd);
	if(ret < 0)
		error_print("mq_close");
	return 0;
}

编译运行:
在这里插入图片描述
消息队列系统限制查看:
在这里插入图片描述


共享内存

在Linux中,POSIX共享内存对象驻留在tmpfs伪文件系统中。系统默认挂载在/dev/shm目录下。 当调用shm_open函数创建或打开POSIX共享内存对象时,系统会将创建/打开的共享内存文件放到/dev/shm目录下。

创建共享内存的基本步骤是:
1.程序执行shm_open函数创建了共享内存区域,此时会在/dev/shm/创建共享内存文件。
在这里插入图片描述
2.通过ftruncate函数改变shm_open创建共享内存的大小为页大小sysconf(_SC_PAGE_SIZE)整数倍,如果不执
ftruncate函数的话,会报Bus error的错误。
在这里插入图片描述
3.通过mmap函数将创建的共享内存文件映射到内存。
在这里插入图片描述
在这里插入图片描述
4.通过munmap卸载共享内存。
在这里插入图片描述
5. 通过shm_unlink删除内存共享文件。
在这里插入图片描述
我们用下面的源程序对POSIX共享内存进行测试,如下shmen_write.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>

void error_print(char *msg)
{
	perror(msg);
	exit(-1);
}

int main (int argc, char *argv[])
{
	int ret, i;
	const char *memname = "/mymem";
	size_t mem_size = sysconf(_SC_PAGE_SIZE);
	int fd = shm_open(memname, O_CREAT|O_TRUNC|O_RDWR, 0666);
	if (fd == -1)
	error_print("shm_open");
	ret = ftruncate(fd, mem_size);
	
	if (ret != 0)
		error_print("ftruncate");
	void *ptr = mmap(0, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (ptr == MAP_FAILED)
		error_print("MMAP");
	close(fd);
	
	for(i = 0; i < 20; i++)
	{
		sprintf((char*)ptr, "data %d", i);
		printf("write data %s\n", (char*)ptr);
	}
	ret = munmap(ptr, mem_size);
	if (ret != 0)
		error_print("munmap");
	ret = shm_unlink(memname);
	if (ret != 0)
		error_print("shm_unlink");
	return 0;
}

编译程序shmen_write.c
在这里插入图片描述
shmen_read.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>

void error_print(char *msg)
{
	perror(msg);
	exit(-1);
}

int main (int argc, char *argv[])
{
	int ret, i;
	const char *memname = "/mymem"; //共享内存文件以/开头,可以更好兼容不同系统
	struct stat statbuf;
	size_t mem_size = 2 * sysconf(_SC_PAGE_SIZE);
	int fd = shm_open(memname, O_CREAT|O_TRUNC|O_RDWR, 0666);
	if (fd == -1)
		error_print("shm_open");
	ret = ftruncate(fd, mem_size);
	
	if (ret != 0)
		error_print("ftruncate");
	void *ptr = mmap(0, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (ptr == MAP_FAILED)
		error_print("MMAP");
		close(fd);
	for(i = 0; i < 20; i++)
	{
		printf("read data %s\n", (char*)ptr);
		sleep(1);
	}
	
	ret = munmap(ptr, mem_size);
	if (ret != 0)
		error_print("munmap");
	ret = shm_unlink(memname);
	if (ret != 0)
		error_print("shm_unlink");
	return 0;
}

编译程序shmen_read.c
在这里插入图片描述
先运行shmem_write,再运行shmem_read。

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/89046966