linux下进程间通信的几种主要手段简介:
1)无名管道:
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,构成两进程间通信的一个媒介
数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道创建:
#include <unistd.h>
int pipe(int fd[2])
管道两端可分别用描述字fd[0]以及fd[1]来描述,管道读端:fd[0],管道写端:fd[1];
2)有名管道:
不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
有名管道的创建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode)
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。
第二个参数与打开普通文件的open()函数中的mode 参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以于FIFO,如close、read、write等等
二.信号
消息队列是一个消息链表,允许一个或多个进程向它写消息,另外的进程从中读取消息,具有FIFO的特性,但是可实现随即访问。在内核中,消息队列由"队列id"标识。
创建消息队列==>添加消息==>读取消息==>删除队列
<1>创建消息队列 msgget()
int msgget(key_t key,int flag); 参数:key 为IPC_PRIVATE时,建立新的消息队列 不为IPC_PRIVATE,根据flag是否有IPC_CREAT确定 flag 标志位 返回:成功,返回消息队列识别代码msgid 失败 返回-1 错误信息存放在errno中 |
<2>向消息队列添加消息
int msgsnd(int msgid,struct msgbuf *msgp,size_t msgsz,int flag); 参数:msgid 消息队列id 由msgget()得到 msgp 存放消息类型和内容
msgbuf在头文件中未实际定义,需要自己定义。 msgsz 消息大小 flag 为IPC_NOWAIT时,未立即发送,调用进程立即返回 返回:成功,返回消息队列识别代码 失败 返回-1 错误信息存放在errno中 |
<3>从消息队列读取消息
int msgrcv(int msgid,struct msgbuf *msgp,size_t msgsz,long msgty,int flag); 参数: msgid 消息队列id 由msgget()得到 msgp 存放消息类型和内容 msgsz 消息大小 msgty 消息类型 flag 为IPC_NOWAIT时,未立即发送,调用进程立即返回 返回:成功,返回消息队列识别代码 失败 返回-1 错误信息存放在errno中
|
<4>控制消息队列
int msgctl(int msgid,int cmd,struct msgid_ds, *buf); 参数: msgid 消息队列id 由msgget()得到 cmd IPC_STAT 将消息队列信息写入buf IPC_SET 根据buf设置为消息队列 IPC_RMID 删除消息队列 返回:成功,返回消息队列识别代码 失败 返回-1 错误信息存放在errno中 |
共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。共享内存往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
首先要用的函数是shmget,它获得一个共享存储标识符。
#i nclude <sys/types.h>
#i nclude <sys/ipc.h>
#i nclude <sys/shm.h>
int shmget(key_t key, int size, int flag);
该函数为获取一个共享内存的标识符,其中key变量可以通过ftok()来获得
Size为需要的共享内存大小,shmflg是共享内存标志:IPC_CREAT、IPC_EXCL、;其中IPC_CREAT用于生成一个新的共享内存段,当IPC_CREAT与IPC_EXCL一起使用时,当所要创建的共享内存段已经存在时,将会返回一个EEXIST错误
void *shmat(int shmid, const void *shmaddr, int shmflg);
该函数用于将一个共享内存段连接到地址空间中,其中shmid为共享内存段的标识符;
shmaddr:
(1) 如果shmaddr为0,则此段连接到由内核选择的第一个可用地址上。
(2) 如果shmaddr非0,并且没有指定SHM_RND,则此段连接到shmaddr所指定的地址上。
(3) 如果shmaddr非0,并且指定了SHM_RMD,则此段连接到(shmaddr-(shmaddr mod SHMLBA))
所表示的地址上。SHM_RND命令的意思是:取整。SHMLBA的意思是:低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数。
除非只计划在一种硬件上运行应用程序,否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为0,以便由内核选择地址。
五.信号量
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
(1) 测试控制该资源的信号量。
(2) 若此信号量的值为正,则允许进行使用该资源。进程将进号量减1。
(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
信号量函数定义如下:
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
semget函数创建一个新的信号量或是获得一个已存在的信号量键值。
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops)
key是一个建我们可以用ftok函数来获得。key是一个整数值,不相关的进程将通过这个值去访问同一信号量。
函数semop用来改变信号量的状态,第一个参数,sem_id,是由semget函数所返回的信号量标识符。第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
}
第一个成员,sem_num,是信号量数目,通常为0,除非我们正在使用一个信号量数组。sem_op成员是信号量的变化量值。(我们可以以任何量改变信号量值,而不只是1)通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。
最后一个成员,sem_flg,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量,如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。
semop的所用动作会同时作用,从而避免多个信号量的使用所引起的竞争条件。我们可以在手册页中了解关于semop处理更为详细的信息
int semctl(int sem_id, int sem_num, int command, ...);
第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。
通常的command值为:
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:当信号量不再需要时用于删除一个信号量标识。