前言
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。