进程间通讯有以下6种:管道、FIFO、消息队列、信号量、共享内存、套接字。
管道:
管道是最简单,效率最差的一种通信方式。
管道本质上就是内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据。
如果想要实现两个进程通过管道来通信,则需要让创建管道的进程fork子进程,这样子进程们就拥有了父进程的文件描述符,这样子进程之间也就有了对同一管道的操作。
缺点:
半双工通信,一条管道只能一个进程写,一个进程读。
一个进程写完后,另一个进程才能读,反之同理。
FIFO
也称为命名管道,它是一种文件类型。
1、特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
消息队列
A进程往消息队列写入数据后就可以正常返回,B进程需要时再去读取就可以了,效率比较高。
而且,数据会被分为一个一个的数据单元,称为消息体,消息发送方和接收方约定好消息体的数据类型,不像管道是无格式的字节流类型,这样的好处是可以边发送边接收,而不需要等待完整的数据。
但是也有缺点,每个消息体有一个最大长度的限制,并且队列所包含消息体的总长度也是有上限的,这是其中一个不足之处。
另一个缺点是消息队列通信过程中存在用户态和内核态之间的数据拷贝问题。进程往消息队列写入数据时,会发送用户态拷贝数据到内核态的过程,同理读取数据时会发生从内核态到用户态拷贝数据的过程。
信号量
信号量(semaphore)是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)
所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。
若为0
,说明
它被占用,测试的
线程要进入睡眠队列中,等待被唤醒
。
互斥锁必须总是由锁住它的线程解锁,信号量的挂出却不必由执行它的等待操作的同一线程执行。说明信号量是互斥锁的升级。
P操作:这个操作会将信号量减一,相减后信号量如果小于0,则表示资源已经被占用了,进程需要阻塞等待;如果大于等于0,则说明还有资源可用,进程可以正常执行。
V操作:这个操作会将信号量加一,相加后信号量如果小于等于0,则表明当前有进程阻塞,于是会将该进程唤醒;如果大于0,则表示当前没有阻塞的进程。
当多个线程在等待同一个信号量时,不能预测V操作会重启哪一个线程。
分为
1.二元信号量 和互斥类似
2.计数信号量 :
posix信号量 的shm_open
3.计数信号量集:
system v信号量 shmget
有posix信号量 和 system v信号量
(都属于 用户态进程使用的信号量)
posix 信号量有 2种
1.posix 有名信号量
:使用posix ipc 名字标识,
其值保存在文件中
,
可用于进程或线程间的同步
。彼此无亲缘关系的不同进程通常使用有名信号量。
2.posix基于内存的信号量
:即无名信号量,
其值保存内存区中,可用于有亲缘关系的进程或线程间的同步
。所以无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。
有名的信号量
#include “semaphore.h”
sem_open
sem_t *
sem_open
(const char *name, int oflag, mode_t mode, unsigned int value);
功能:创建或打开一个信号量
参数说明:
name
:信号量的名字,标识信号量;
可用函数px_ipc_name, 原型char *px_ipc_name(const char *name)
这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建
在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都
是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。
oflag
参数可以为:0,O_CREAT,O_CREAT | O_EXCL。
如果为0表示打开一个已存在的信号量
如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回。此时mode和value需要指定。
如果为O_CREAT | O_EXCL,表示如果信号量已存在会返回错误。
mode
参数:用于创建信号量时,表示信号量的权限位,和open函数一样包括:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IWOTH。或者是用数字表示,如可以是0666. 如果指定了O_CREAT标志,则mode、value是需要的。
value:信号量的初始值,一般是1
. 只有当所需的信号量尚未存在时才初始化。可以理解为如果存在信号量了,该值无效
该函数成功后,
会在/dev/shm下生成一个sem.xxx的文件。xxx表示name。
举例:
开始:让主进程调用sem_open函数把oflag设置为O_CREAT。其他进程设置调用sem_open函数把oflag设置为0。
结束:主进程调用
sem_close和sem_unlink函数,而其他进程只调用sem_close函数即可。
sem_close
int
sem_close
(sem_t *sem);
功能:sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。
个人自己见解:
相当于计数减1.但就算计数减到0,也不能自动删除信号量,需要调用sem_unlink()才能删除(前提是计数已为0)
。可以通过sem_close函数或进程终止办法使计数减1。
返回值:成功返回0,失败返回-1
调用sem_close关闭信号量并没有把它从系统中删除它,POSIX有名信号量是随内核持续的。即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。
sem_unlink
int
sem_unlink
(const char *name);
功能:
将有名信号量立刻从系统中删除(立即从/dev/shm目录下删除文件)
,但信号量的析构是在所有进程都关闭信号量的时候(只close是不够的!)也就是要等待最后一个sem_close发生。
返回值:成功返回0,失败返回-1
sem_unlink会马上删除指定的信号量名,但要等到所有打开该信号量的进程关闭该信号量后才删除该信号。详细地说,当进程创建一个有名信号量,会在/dev/shm下生成一个sem.xxx的文件,所有打开该信号量的进程(包括创建它的进程)都会增加该文件的引用计数,并且这个计数由内核管理。当调用sem_unlink时,/dev/shm下的sem.xxx文件会马上被删除,但是信号量本身并没有被删除,所有已打开该信号量的进程仍能正常使用它。直到所有打开该信号量的进程关闭该信号量后,内核才真正删除信号量。个人见解:
所以应该只让一个主进程调用sem_unlink函数,其他每个进程调用sem_close函数计数减1.
int
sem_trywait
(sem_t *sem);
int
sem_wait
(sem_t *sem);
int
sem_timedwait
(sem_t * sem, const struct timespec time);
sem_wait :该操作会检查信号量的值,
如果其值小于或等于0,那就阻塞
,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。这整个操作必须是一个原子操作
sem_trywait为非阻塞版本
,当信号量为0时,该函数返回-1并且将errno置为EAGAIN
sem_timedwait带有超时功能,超时到期并且信号量计数没能减1,sem_timedwait将返回-1且将errno设置为ETIMEDOUT。(毕竟一直阻塞也不是办法。
int
sem_post
(sem_t *sem);
该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。该操作也必须是一个原子操作。
int sem_t
getvalue
(sem_t sem, int *val);
获取信号量的值,一般只用来调试。
举例:
开始:让主进程调用sem_open函数把oflag设置为O_CREAT。其他进程设置调用sem_open函数把oflag设置为0。
结束:主进程调用
sem_close和sem_unlink函数,而其他进程只调用sem_close函数即可。
//主进程
#include "mainwindow.h"
#include <qdebug.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
// create & initialize semaphore
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED)
{
qDebug("unable to create semaphore");
sem_unlink(SEM_NAME);
return;
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid<0)
{
qDebug("failure in shmget");
return;
}
// attach this segment to virtual memory
shm =(char*) shmat(shmid,NULL,0);
//start writing into memory
s = shm;
for(ch='A';ch<='Z';ch++)
{
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while(*shm != '*')
{
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
qDebug("server :semaphore end");
}
//其他进程
#include "mainwindow.h"
#include <qdebug.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
qDebug("reader:unable to execute semaphore");
sem_close(mutex);
return ;
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,0666);
if(shmid<0)
{
qDebug("reader:failure in shmget");
return ;
}
//attach this segment to virtual memory
shm = (char*)shmat(shmid,NULL,0);
//start reading
s = shm;
for(s=shm;*s!=NULL;s++)
{
sem_wait(mutex);
qDebug("%c",*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
qDebug("reader:semaphore end");
}
无名信号量(基于内存的信号量)
不会在/dev/shm目录下创建文件。
用途:
1.可用于有亲缘关系的进程同步。
主要是父子进程间,需要用到fork函数、mmap函数和匿名映射。
//在qt中编写代码
#include "mainwindow.h"
#include <qdebug.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//互斥 只需要 定义一个信号量
sem_t *sem;
//匿名映射mmap MAP_ANONYMOUS -1不用打开文件
sem = (sem_t*)mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
//信号量 sem 初始化 第一个1表示进程间同步互斥 第二个1信号量的初始值1
sem_init(sem, 1, 1);
pid_t pid = fork();
if(pid == 0){//子进程
//p 操作-1
sem_wait(sem);
qDebug("hello");
//v 操作+1
sem_post(sem);
}
else if(pid > 0){//父进程
sem_wait(sem);
qDebug("world");
sem_post(sem);
}
//回收子进程
wait(NULL);
//销毁mmap
munmap(sem, sizeof(sem_t));
//销毁信号量
sem_destroy(sem);
}
2.线程间的同步。
直接在创建主线程外面创建 sem_t 变量。
//在qt中编写代码
#include "mainwindow.h"
#include <qdebug.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id);
qDebug("thread_one have the semaphore\n");
number++;
qDebug("number = %d\n",number);
sem_post(&sem_id);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id);
qDebug("thread_two have the semaphore \n");
number--;
qDebug("number = %d\n",number);
sem_post(&sem_id);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id, 0, 1);
pthread_create(&id1,NULL,thread_one_fun, NULL);
sleep(1);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
qDebug("main,,,\n");
}
头文件: <semaphore.h>
sem_init
int
sem_init
(sem_t *sem, int pshared,unsigned value);
功能:初始化指定的信号量
返回值:成功 0 ; 错误 错误号。
参数说明:sem:信号量变量名;参数value为信号量的初始值。
参数pshared用于说明信号量的共享范围,
如果pshared为0
,那么该信号量只能由初始化这个信号量的进程中的
线程使用
,如果
pshared非零
,任何可以访问到这个信号量的
进程都可以使用
这个信号量。
必须保证只调用sem_init()进行初始化一次,对于一个已初始化过的信号量调用sem_init()的行为是未定义的
sem_destroy
int
sem_destroy
(sem_t *sem);
功能:销毁一个指定的信号量
返回值:成功 0 ; 错误 错误号。
参数说明:sem:信号量变量名(sem_init内值)
摧毁一个有线程阻塞在其上的信号量的行为是未定义的。
其他的操作,如
等待(
sem_wait
)、挂出(
sem_post
)
、
获取信号量值(
getvalue
)
都是和有名信号量一样的
System V信号量
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。
在semop函数中,sembuf结构的定义如下:
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
其中 sem_op 是一次操作中的信号量的改变量:
-
若sem_op > 0,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则唤醒它们。
-
若sem_op < 0,请求 sem_op 的绝对值的资源。
- 如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。
- 当相应的资源数不能满足请求时,这个操作与sem_flg有关。
- sem_flg 指定IPC_NOWAIT,则semop函数出错返回EAGAIN。
-
sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
-
当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
此信号量被删除,函数smeop出错返回EIDRM;
进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR
-
当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
-
若sem_op == 0,进程阻塞直到信号量的相应值为0:
- 当信号量已经为0,函数立即返回。
-
如果信号量的值不为0,则依据sem_flg决定函数动作:
-
sem_flg指定IPC_NOWAIT,则出错返回EAGAIN。
sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;
此信号量被删除,函数smeop出错返回EIDRM;
进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR
-
sem_flg指定IPC_NOWAIT,则出错返回EAGAIN。
在semctl函数中的命令有多种,这里就说两个常用的:
- SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
- IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
POSIX 信号量与SYSTEM V信号量的比较
1. 对POSIX来说,信号量是个非负整数。常用于线程间同步。
而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,
这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。
2.POSIX信号量的引用头文件是“<semaphore.h>”,而SYSTEM V信号量的引用头文件是
“<sys/sem.h>”。
共享内存
共享内存解决了消息队列存在的内核态和用户态之间数据拷贝的问题。
现代操作系统对于内存管理采用的是虚拟内存技术,也就是说每个进程都有自己的虚拟内存空间,虚拟内存映射到真实的物理内存。共享内存的机制就是,不同的进程拿出一块虚拟内存空间,映射到相同的物理内存空间。这样一个进程写入的东西,另一个进程马上就能够看到,不需要进行拷贝。因为共享内存不涉及内核。
基本的函数介绍:
mmap()
系统调用使得进程之间通过映射同一个普通文件实现共享内存.
void*
mmap
( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
相当于在windows的
mapviewoffile
函数的作用
addr :指向欲映射的内存起始地址,
通常设为 NULL
,代表让系统自动选定地址,映射成功后返回该地址。
len :映射到调用进程地址空间的字节数。
prot : 映射区域的保护方式。可以为以下几种方式的组合:
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取
flags :MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容.
MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,此时fd = -1。
fd: 要映射到内存中的文件描述符。如果使用匿名内存映射时,即
flags中设置了MAP_ANONYMOUS,fd设为-1
。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。一般情况下,需要提前获取到文件描述符fd。
offset:文件映射的偏移量,
通常设置为0
。
返回值:
若映射成功则返回映射区的
内存起始地址
,否则返回MAP_FAILED(-1),错误原因存于errno 中.
mmap 用于共享内存的两种方式
(1)使用普通文件提供的内存映射
fd=open(name, flag, mode); //
name 为本地磁盘的某一文件
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
优点:无论有无血缘关系的进程间都可以进行通信
缺点:需要在每个进程中分别创建内存映射区,但是这些进程的内存映射区必须关联相同的磁盘文件才能实现进程间的数据同步。
空间大、效率慢。
(2) 使用特殊文件提供匿名内存映射,有2种办法
。(
只适用于父子进程
)
1. BSD 提供匿名映射的办法是fd =-1,同时 flag 指定为MAP_SHARE|MAP_ANON。
ptr = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON,-1,0);
2.SVR4 提供匿名映射的办法是 open
/dev/zero
设备文件,把返回的文件描述符,作为mmap的fd参数。
fd = open(”/dev/zero”,O_RDWR);
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
这种办法类似于使用页交换文件作为后备存储器。
munmap()
int munmap( void * addr, size_t len )
该调用在进程地址空间中解除一个映射关系。相当于windows的
unmapviewoffile
addr是调用mmap()时返回的地址,
len是映射区的大小。
当映射关系解除后,对原来映射地址的访问将导致段错误发生。
msync()
int msync ( void * addr , size_t len, int flags)
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。
flags:
MS_ASYNC:异步
MS_SYNC:同步
具体模式:
posix 共享内存区
posix 内存区对象:内存映射文件和共享内存区对象
内存映射文件:
使用open,再用mmap
每个进程都需要open打开本地磁盘文件
缺点: 需要在每个进程中分别创建内存映射区,且需要关联一个磁盘文件才能实现进程间数据通信;效率低。
//A进程代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
// 1. 打开一个磁盘文件
int fd = open("./english.txt", O_RDWR);
// 2. 创建内存映射区
void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
const char* pt = "==================我是你爹, 你是我儿子吗???****************";
memcpy(ptr, pt, strlen(pt)+1);
// 释放内存映射区
munmap(ptr, 4000);
return 0;
}
//B进程代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
// 1. 打开一个磁盘文件
int fd = open("./english.txt", O_RDWR);
// 2. 创建内存映射区
void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
// 读内存映射区
printf("从映射区读出的数据: %s\n", (char*)ptr);
// 释放内存映射区
munmap(ptr, 4000);
return 0;
}
共享内存区对象:
使用shm_open,再用mmap
shm_open函数
会在/dev/shm 目录下创建共享内存文件
。
共享内存不同于内存映射区,它不属于任何进程,不受进程生命周期的影响;在所有进程间通信的方式中共享内存的效率是最高的。共享内存默认不阻塞,
当多个进程同时读写共享内存,可能会导致数据混乱
;
共
享内存需要借助其他机制(信号量)保证进程间的数据同步。
shm_open
int shm_open(const char *name, int oflag, mode_t mode); 类似windows的createfilemapping
功能说明
:用于创建或者打开共享内存文件.
shm_open一定要把文件放在tmpfs文件系里,常见的Linux发布版存放目录就是
/dev/shm
.
name
:要打开或创建的共享内存文件名,由于shm_open打开或操作的文件都是位于/dev/shm目录的,因此name不能带路径,例如:/var/myshare 这样的名称是错误的,而 myshare 是正确的
oflag
:打开的文件操作属性:O_RDONLY、O_RDWR、O_CREAT、O_EXCL、O_TRUNC的按位或运算组合
mode
:文件共享模式,必须要指定。例如 0777 。如果没有指定O_CREAT,可以把mode设置成0
shm_unlink
int shm_unlink(const char *name);
删除/dev/shm目录的文件,
shm_unlink
删除的文件是由shm_open函数创建于/dev/shm目录的,
个人感觉应该有只能由一个主服务器去使用维护,其他的客户端不需要使用。
ftruncate
int ftruncate(int fd, off_t length);
功能说明
:重置文件大小。任何open打开的文件都可以用这个函数,不限于shm_open打开的文件。
一般关闭需要调用close()函数对fd进行关闭。
这个代码是无法在qt执行,只用终端写的,并且需要加上-lrt 。即通过g++ -g shm.c -o shm -lrt 编译运行。
在测试过程中暂时去掉shm_unlink函数,目的就是让读线程能够执行获取到共享区。
//写进程
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define MMAP_DATA_SIZE 1024
#define USE_MMAP 1
int main(int argc,char * argv[])
{
char * data;
int fd = shm_open("shm-file0001", O_CREAT|O_RDWR, 0777);
if (fd < 0) {
printf("shm_open failed!\n");
return -1;
}
ftruncate(fd, MMAP_DATA_SIZE);
if (USE_MMAP) {
data = (char*)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (!data) {
printf("mmap failed\n");
close(fd);
}
sprintf(data, "This is a share memory! %d\n", fd);
munmap(data, MMAP_DATA_SIZE);
}
else {
char buf[1024];
int len = sprintf(buf,"This is a share memory by write! ! %d\n",fd);
if (write(fd, buf, len) <= 0) {
printf("write file %d failed!%d\n",len,errno);
}
}
close(fd);
getchar();
shm_unlink("shm-file0001");//该函数要在读进程执行完close(fd)后,才能执行
return 0;
}
//读进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define MMAP_DATA_SIZE 1024
int main(int argc,char * argv[])
{
char * data;
int fd = shm_open("shm-file0001", O_RDWR, 0777);
if(fd < 0)
{
printf("error open shm object\n");
return -1;
}
data = (char*)mmap(NULL, MMAP_DATA_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (!data) {
printf("mmap failed!\n");
close(fd);
return -1;
}
printf(data);
munmap(data,MMAP_DATA_SIZE);
close(fd);
getchar();
return 0;
}
测试结果
gxrong@gxrong-virtual-machine:~/qt$ gcc -g shm.c -o shm -lrt
gxrong@gxrong-virtual-machine:~/qt$ ./shm
gxrong@gxrong-virtual-machine:~/qt$ gcc -g client.c -o client -lrt
client.c: In function ‘main’:
client.c:30:9: warning: format not a string literal and no format arguments [-Wformat-security]
printf(data);
^~~~~~
gxrong@gxrong-virtual-machine:~/qt$ ./client
This is a share memory! 3
system v 共享内存区
创建一个新的共享内存区或访问一个已存在的共享内存区。
不会在/dev/shm创建文件
shmget
int
shmget
(key_t key, size_t size, int shmflg);
相当于shm_open 和ftruncate
key:
如果两个进程没有任何关系,所以就用ftok()算出来一个标识符(或者自己定义一个如1000)使用了。
如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。
该函数的函数原型如下:
// ftok函数原型
#include <sys/types.h>
#include <sys/ipc.h>
// 将两个参数作为种子, 生成一个 key_t 类型的数值
key_t ftok(const char *pathname, int proj_id);
pathname: 当前操作系统中一个存在的路径
proj_id: 这个参数只用到了 int 中的一个字节,传参的时候要将其作为 char 进行操作,取值范围: 1-255
// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);
size: 共享存储段的字节数.如果是访问一个已存在的共享内存区,可以是0
flag: 读写的权限还有IPC_CREAT或 O_CREAT|O_EXCL、SHM_R 、SHM_W 其中0666(所有用户可读可写)如
IPC_CREAT | 0666
返回值: 成功返回共享存储的id,失败返回-1
//创建一块大小为 4k 的共享内存
shmget(100, 4096, IPC_CREAT|0664);
shmat
获取第一个可用共享内存空间的地址
void *
shmat
(int shmid, const void *addr, int flag);
相当于mmap
shmid:共享存储的id 即
shmget函数返回值
addr:一般为null,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整
flag:如前所述,一般为0 //推荐值
返回值:如果成功,返回共享存储段地址,出错返回-1
shmdt
当不需要对此共享内存进行操作时候,调用shmdt函数进行分离
int
shmdt
(void *addr);
相当于 munmap
addr:共享存储段的地址,以前
调用shmat时的返回值
shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1
shmctl
可以设置、获取共享内存的状态也可以将共享内存标记为删除状态。当共享内存被标记为删除状态之后,并不会马上被删除,
直到所有的进程全部和共享内存解除关联,共享内存才会被删除
。因为通过 shmctl () 函数只是能够标记删除共享内存,所以在程序中多次调用该操作是没有关系的。
int
shmctl
(int shmid,int cmd,struct shmid_ds *buf)
IPC_RMID命令 相当于shm_unlink 和close
shmid:共享存储段的id 即
shmget函数返回值
cmd:一些命令,有:IPC_STAT,
IPC_RMID
,SHM_LOCK,SHM_UNLOCK
//主进程
#include "mainwindow.h"
#include <qdebug.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
// create & initialize semaphore
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED)
{
qDebug("unable to create semaphore");
sem_unlink(SEM_NAME);
return;
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid<0)
{
qDebug("failure in shmget");
return;
}
// attach this segment to virtual memory
shm =(char*) shmat(shmid,NULL,0);
//start writing into memory
s = shm;
for(ch='A';ch<='Z';ch++)
{
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while(*shm != '*')
{
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
qDebug("server :semaphore end");
}
//其他进程
#include "mainwindow.h"
#include <qdebug.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
qDebug("reader:unable to execute semaphore");
sem_close(mutex);
return ;
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,0666);
if(shmid<0)
{
qDebug("reader:failure in shmget");
return ;
}
//attach this segment to virtual memory
shm = (char*)shmat(shmid,NULL,0);
//start reading
s = shm;
for(s=shm;*s!=NULL;s++)
{
sem_wait(mutex);
qDebug("%c",*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
qDebug("reader:semaphore end");
}
一般是共享内存和信号量一起使用的。
在qt 无法使用shm_open,只能使用shmget技术。不知道怎解决。
共享内存区对象
和 内存映射区 都可以实现进程间通信 ,其两者是有区别的:
实现进程间通信的方式:
shm: 多个进程只需要一块共享内存,共享内存不属于进程,需要和进程关联才能使用;
内存映射区: 位于每个进程的虚拟地址空间中,且需要关联一个磁盘文件才能实现进程间数据通信;
效率:
shm: 直接对内存操作,效率高;
内存映射区: 需要内存和文件之间的数据同步 ,效率低;
生命周期:
shm: 进程退出对共享内存没有影响 , 调用相关函数/命令/关机 才能删除共享内存
内存映射区: 进程退出,内存映射区没了
数据完整性(突然断电)
shm: 数据直接存储在物理内存上 ,断电后系统关闭,内存数据丢失;
内存映射区: 可以完整的保存数据 ,内存映射区数据会同步到磁盘文件;
进程间通信方式总结:
- 管道:速度慢,容量有限,只有父子进程能通讯;
- FIFO:任何进程间都能通讯,但速度慢;
- 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题;
- 共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题;相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
- 信号量:不能传递复杂消息,只能用来同步。用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
文件内存映射和传统I/O比较
#1比传统I/O少了一次CPU拷贝,其余的其实也没什么可比较的
#2 文件映射内存对文件大小有限制,所以映射的大小不一定可以和文件大小一致。
和传统I/O相比,少了一步内核空间到用户空间的数据CPU拷贝